一、什么是 JVM?
Java Virtual Machine:Java 程序的运行环境(Java 二进制字节码的运行环境)。
优点:
- 一次编写,到处运行;
- 自动内存管理,垃圾回收功能;
- 数组下标越界检查;
- 多态
1、JVM,JRE,JDK
2、常见的 JVM
3、JVM 内部结构
二、程序计数器
Program Counter Register 程序计数器,用来存储 jvm 指令的地址,是一个寄存器。
- 作用:记住下一条 jvm 指令的执行地址。
- 特点:程序计数器是线程私有的,即每个线程都有,线程阻塞后其值需要保存。不会存在内存溢出。(内存溢出 OOM:指程序申请内存时,没有足够的内存供申请者使用;内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间)
1、工作原理
Java 源码通过编译后生成二进制字节码(即 jvm 指令),这些二进制字节码无法直接让 CPU 运行,需要通过解释器解释成机器码后 CPU 才能执行。而程序计数器用来存储解释器下一次解释的 jvm 指令地址。
Java 编译和解释
Java 是一种特殊的高级性语言,它既有解释性语言的特征,也有编译性语言的特征,因为它是经过先编译,后解释的过程。HelloWorld.java → HelloWolrd.class → 特定平台的机器代码。
- 编译是将源程序翻译成二进制字节码,这种机器码是不可以直接执行的。
- 解释程序不产生目标代码,它逐条地取出程序中的语句,边解释,边执行;解释器把字节码文件边解释成机器语言边交给 CPU 执行。
三、虚拟机栈
Java Virtual Machine Stacks:Java 虚拟机栈。
- 每个线程运行时所需要的内存,称为虚拟机栈。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存。
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
1、使用 Debug 观察虚拟机栈
使用 Idea 对下面的程序以 Debug 方式执行,观察虚拟机栈的情况。
public class Jvm {
public static void main(String[] args) {
a();
}
public static void a() {
b(1, 2);
}
public static int b(int a, int b) {
int c = a + b;
return c;
}
}
Frames 反应虚拟机栈的情况,每调用一个方法都会在顶部添加一行(即 栈顶,每一行就是一个栈帧),当前方法执行完后,当前行会删除,然后执行下一行的方法。最顶部的方法就是当前线程的活动栈帧。
2、设置虚拟机栈的大小
通过下面递归死循环,来判断默认情况下虚拟机栈能存储的栈帧数。
public class Jvm {
static int count = 0;
// 栈帧过多导致栈内存溢出 java.lang.StackOverflowError
public static void main(String[] args) {
try {
a();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
public static void a() {
count++;
a();
}
}
上面的代码运行结果都是 2万 左右,修改虚拟机栈的大小后,再次执行,结果只是 2700 左右。(m,k,没有单位是 b)
3、三个问题
(1)垃圾回收是否涉及栈内存?
栈内存无非是一次次的方法调用,所产生的栈帧内存。而栈帧内存在每一次方法调用结束后都会弹出栈并释放内存,所以不需要垃圾回收来管理。
(2)栈内存分配越大越好吗?
栈内存分配越大可以增加栈帧的容量,但是每个线程所需的内存也随之增大,物理内存不变的情况下,会影响线程数。
(3)方法内的局部变量是否线程安全?
- 如果方法内,局部变量没有逃离方法的作用范围,它是线程安全的。
- 如果是局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全。
// 安全
public static void m1() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(1);
System.out.println(stringBuilder.toString());
}
// 不安全
public static void m2(StringBuilder stringBuilder) {
stringBuilder.append(1);
System.out.println(stringBuilder.toString());
}
// 不安全
public static StringBuilder m3() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(1);
return stringBuilder;
}
4、线程运行诊断
CPU 占用过多
- 用 top 定位哪个进程对 cpu 的占用过高。
- ps H -eo pid,tid,%cpu | grep 进程id (使用 ps 命令进一步定位是哪个线程引起的 cpu 占用过高)
- jstack 进程id:将 java 进程中的所有线程列出来,可以根据线程 id 找到有问题的线程,进一步定位到问题代码的源码行号。
程序运行很长时间没有结果
- jstack 进程id:看有没有发生死锁。
四、本地方法栈
本地方法栈是 Java 虚拟机调用一些本地方法时,需要给本地方法提供的一些内存空间。本地方法(Native Method):不是由 java 代码编写的方法。
五、堆(Heap)
通过 new 关键字,创建对象都会使用堆内存。
- 它是线程共享的,堆中对象都需要考虑线程安全的问题。
- 有垃圾回收机制。
1、设置堆大小
运行一下代码会出现堆内存溢出:java.lang.OutOfMemoryError: Java heap space。
public class Jvm {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a);
a = a + a;
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
上面的代码运行结果是 25,修改堆的大小,再次执行,结果是 17。
2、堆内存诊断
演示堆内存使用情况的代码,在不同时间观察内存的使用情况。
public class Jvm {
public static void main(String[] args) throws InterruptedException {
System.out.println("1");
Thread.sleep(30000);
// 10 MB
byte[] array = new byte[1024 * 1024 * 10];
System.out.println("2");
Thread.sleep(20000);
array = null;
System.gc();
System.out.println("3");
Thread.sleep(1000000L);
}
}
(1)jps 工具
查看当前系统中有哪些 java 进程以及该进程的进程号。
C:\software\IntelliJ IDEA Workspace\algorithm>jps
16160
1808 Jvm
19104 Jps
9440 Launcher
(2)jmap 工具
查看堆内存占用情况,jmap -heap 进程id。
C:\software\IntelliJ IDEA Workspace\algorithm>jmap -heap 1808
Attaching to process ID 1808, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.211-b12
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2090860544 (1994.0MB)
NewSize = 44040192 (42.0MB)
MaxNewSize = 696778752 (664.5MB)
OldSize = 88080384 (84.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 33554432 (32.0MB)
used = 4698000 (4.4803619384765625MB)
free = 28856432 (27.519638061523438MB)
14.001131057739258% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
(3)jconsole 工具
图形界面的,多功能的监测工具,可以连续监测。
(4)jvisualvm 工具
如果垃圾回收后,内存占用仍然很高。则使用 jvisualvm 的 堆Dump 功能查看堆的使用情况。
public class Jvm {
public static void main(String[] args) throws InterruptedException {
List<Studnet> studnets = new ArrayList<>();
for (int i = 0; i < 200; i++) {
studnets.add(new Studnet());
}
Thread.sleep(1000000000L);
}
}
class Studnet {
private byte[] big = new byte[1024 * 1024];
}
标题:JVM 的内部结构
作者:Yi-Xing
地址:http://47.94.239.232/articles/2021/01/21/1611226534659.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!