定义:
Java Virtual Machine
- java 程序的运行环境(java 二进制字节码的运行环境)
好处:
- 编写,到处运行
- 内存管理,垃圾回收功能
- 下标越界检查
- 多态
常见的JVM
内存结构
程序计数器
定义(维基百科):
(英语:Program Counter,PC
)是一个中央处理器中的寄存器,用于指示计算机在其程序序列中的位置。在Intel x86和Itanium微处理器中,它叫做指令指针(instruction pointer,IP
),有时又称为指令地址寄存器(instruction address register,IAR)[1]、指令计数器[2]或只是指令序列器的一部分[3]。
在大部分的处理器中,指令指针都是在提取程序指令后就被立即增加;也就是说跳跃指令的目的地址,是由跳跃指令的操作数加上跳跃指令之后下一个指令的地址(单位为比特或字节,视电脑形态而定)来获得目的地。
处理器通常从存储器中顺序获取指令,但控制传输指令通过在PC中添加一个新值来改变顺序。这些包括“分支”(有时称为“跳转”),“子例程调用”和“返回”。以某些断言结果为真为条件的传输可让计算机在不同条件下遵循不同的顺序。
“分支”规定下一条指令从内存中的其他地方获取。“子程序”不仅调用分支,而且还保存 PC 的先前内容。“返回”检索 PC 的保存内容并将其放回去,然后按照子程序调用的指令继续顺序执行。
- 作用,是记住下一条jvm指令的执行地址
- 特点
- 是线程私有的
- 不会存在内存溢出
作用
CPU
在执行程序的时候,进行来回切换,程序中有多个线程,这时就需要记录当前执行到哪里了,每个线程都有自己的程序计数器
线程1 线程2
结构
虚拟机栈
定义(Java Virtual Machine Stacks
)
下面的内容来源于引用
JVM 栈,也称为线程栈,是JVM 内存中为单个执行线程创建的数据区域。线程使用 JVM 堆栈来存储局部变量、部分结果以及方法调用和返回的数据。
什么是堆栈帧? JVM 堆栈数据区实际上被组织为帧堆栈。框架的生命周期如下所示:
调用新方法时会创建一个新栈帧(Frame
)。
新栈帧被添加到堆栈顶部,执行控制被转移到新方法。
新栈帧在新方法的执行过程中用于存储局部变量、部分结果以及用于后续方法调用和返回的数据。
当执行方法结束时,新栈帧从堆栈顶部移除。
什么是 StackOverflowError
? StackOverflowError
是线程抛出的异常,当它的堆栈没有更多空间来添加新框架以进行下一个方法调用时。您可以使用 JVM -Xss 选项来增加 JVM 堆栈大小以避免 StackOverflowError
异常。
当运行的线程过多时,为分配JVM栈预留的内存区域也可能变满,导致OutOfMemoryError
异常。不幸的是,我没有看到任何 JVM 选项来增加 JVM 堆栈区域以避免OutOfMemoryError
异常。
下图说明了JVM栈区、栈和帧:
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
栈内存溢出
- 栈帧过多导致栈内存溢出
- 栈帧过大导致栈内存溢出
异常
栈内存溢出 java.lang.StackOverflowError
参数
-Xss256k
线程运行诊断
案例1: cpu
占用过多
定位
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
jstack
进程id- 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
案例2:程序运行很长时间没有结果
定位
jstack
本地方法栈
结构
Object
类中 带有native
例如:clone()、wait()、getClass()
等
堆
定义(Heap 堆)
通过 new 关键字,创建对象都会使用堆内存
特点
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
结构
异常
堆内存溢出 java.lang.OutOfMemoryError: Java heap space
参数
-Xmx8m
方法区
定义
Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于常规语言的编译代码的存储区或类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括在类和实例初始化和接口初始化中使用的特殊方法(第 2.9 节)。方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但简单的实现可能会选择不进行垃圾收集或压缩它。本规范不强制要求方法区的位置或用于管理编译代码的策略。方法区可以是固定大小,也可以根据计算需要进行扩展,如果不需要较大的方法区,可以进行收缩。方法区的内存不需要是连续的。 Java 虚拟机实现可以为程序员或用户提供对方法区初始大小的控制,以及在可变大小方法区的情况下,对最大和最小方法区大小的控制。以下异常情况与方法区相关联:如果方法区中的内存无法用于满足分配请求,则 Java 虚拟机将抛出 OutOfMemoryError。
组成
JDK1.6
JDK1.8
异常
JDK 1.6 java.lang.OutOfMemoryError: PermGen space
永久代(PermGen space)
JDK 1.8 java.lang.OutOfMemoryError: Metaspace
元空间(Metaspace)
参数
-XX:MaxMetaspaceSize=8m
常量池
编写代码
public class Hello {
public static void main(String[] args) {
System.out.println("HelloWord");
}
}
编译成.class
文件,找到该目录,cmd
运行javap -v .\Hello.class
,整个结构通过查表方式进行查找。
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量
等信息 - 运行时常量池,常量池是
*.class
文件中的,当该类被加载,它的常量池信息就会放入运行时常量
池,并把里面的符号地址变为真实地址
StringTable特性
- 常量池中的字符串仅是符号,第一次用到时才变为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是 StringBuilder (1.8)
- 字符串常量拼接的原理是编译期优化
- 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
- 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
- 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
面试题1
/**
* 演示字符串相关面试题
* JDK1.8
*/
public class Demo1_21 {
// TODO JDK1.8
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // ab
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern();
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
String x1 = "cd";
System.out.println(x1 == x2); // false
}
}
下面对比JDK1.6和JDK1.8
面试题2
注意:JDK1.6入常量池将堆中的对象拷贝了一份(副本),将副本放入常量池中,并非原有的对象
JDK 1.6
/**
* 演示字符串相关面试题
* JDK1.6
*/
public class Demo1_21 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
x2.intern(); // "cd" // JDK1.6拷贝的副本
String x1 = "cd";
System.out.println(x1 == x2); // false
}
}
JDK1.8
/**
* 演示字符串相关面试题
* JDK1.8
*/
public class Demo1_21 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // ab
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern();
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
x2.intern();
String x1 = "cd";
System.out.println(x1 == x2); // true
}
}
结论
我们看JDK1.6和JDK1.8最后一个 x1 == x2
的比较,JDK1.6为false
,JDK1.8为true
.
JDK1.6入常量池将堆中的对象拷贝了一份(副本),将副本放入常量池中,并非原有的对象
参数
JDK1.6 -XX:MaxPermSize=10m
JDK1.8 -Xmx10m -XX:-UseGCOverheadLimit
StringTable垃圾回收
参数
-Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
StringTable底层类似Hash表的实现方式,Hash表数据结构是数组加链表,每个数组的个数叫做桶(buckets)
Heap
PSYoungGen total 2560K, used 1805K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 88% used [0x00000000ffd00000,0x00000000ffec3460,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
Metaspace used 3206K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 349K, capacity 388K, committed 512K, reserved 1048576K
SymbolTable statistics:
Number of buckets : 20011 = 160088 bytes, avg 8.000
Number of entries : 13321 = 319704 bytes, avg 24.000
Number of literals : 13321 = 592600 bytes, avg 44.486
Total footprint : = 1072392 bytes
Average bucket size : 0.666
Variance of bucket size : 0.668
Std. dev. of bucket size: 0.817
Maximum bucket size : 6
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 1743 = 41832 bytes, avg 24.000
Number of literals : 1743 = 177728 bytes, avg 101.967
Total footprint : = 699664 bytes
Average bucket size : 0.029
Variance of bucket size : 0.029
Std. dev. of bucket size: 0.171
Maximum bucket size : 3
StringTable性能调优
- 调整 -XX:StringTableSize=桶个数
- 考虑将字符串对象是否入池
直接内存
定义
Direct Memory
- 常见于 NIO 操作时,
- 用于数据缓冲区 分配回收成本较高,
- 但读写性能高 不受 JVM 内存回收管理
原理
- 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
- ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调 用 freeMemory 来释放直接内存
参数
禁用对System.gc()方法
-XX:+DisableExplicitGC
引用
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
http://www.herongyang.com/JVM/Stack-Overflow-What-Is-JVM-Stack.html