前言
JVM 是 Java 程序的运行环境,学习 JVM,方能了解 Java 程序是如何被执行的,为进一步深入底层原理乃至程序性能调优打好基础。
- JVM(Java Vitual Machine):Java虚拟机 ,负责与操作系统交互
- JRE(Java Runtime Environment):Java运行环境 = JVM + 基础类库
- JDK(Java Development Key):Java开发工具包 = JVM + 基础类库 + 编译工具
- 开发JavaSE程序 = JDK + IDE集成开发工具
- 开发JavaEE程序 = JDK + IDE集成开发工具 + 应用服务器
JVM内存结构
程序计数器(寄存器)Program Counter Register
记住下一条jvm指令的执行地址,线程私有的,不会内存溢出
本地方法栈 Native Method Stacks
Object类中native修饰符是一个非Java语言编写的调用本地方法接口,线程私有的
虚拟机栈(方法栈)JVM Stacks
线程运行需要的内存空间,每个方法是一个栈帧,运行时需要内存,每个线程只有一个活动栈帧,对应着正在执行的方法,存储方法的参数以及局部变量,垃圾回收不涉及方法栈,线程私有的
方法区 Method Area
用于存储类变量以及主动使用的类成员变量,在JDK1.6之前是永久代的存储空间,而后迁移到本地内存的元空间中,线程共享的
堆内存 Heap
通过new关键字创建对象会使用堆内存,有垃圾回收机制(避免对象创建过多OM),StringTable的对象存储在这里,线程共享的,需要考虑线程安全问题
内存诊断
top
- 查看当前系统中的所有进程
jps
- 查看当前系统中有哪些Java进程
jstack
- 查看当前系统中有哪些Java栈
jmap -heap 进程(Process)id
- 查看堆内存占用情况
jconsole
- 图形界面的多功能检测工具,可以连续检测
jvisualvm
- 图形界面的多功能检测工具,功能更强大
StringTable
StringTable
是HashTable的结构
JDK1.8 串池StringPool
存放着String字符串对象的引用,作用是避免加入相同内容的字符串对象到StringTable
中,因此StringTable
内部存储的是不重复的字符串对象。
JDK1.6 当加入String字符串对象时,直接访问StringTable
是否有相同内容的字符串对象,没有再向StringTable
中添加,从而StringTable
内部存储的是不重复的字符串对象。
主动调用的String字符串对象才会被置入StringTable
中,动态生成的String字符串对象不会置入StringTable
,但是可以调用intern()方法主动将字符串对象置入StringTable
。
JDK1.8 调用intern()方法是直接将调用该方法的字符串尝试添加到StringTable
中,串池StringPool
放在元空间的常量池,StringTable
放在堆内存,滥用会导致堆内存溢出。
JDK1.6 调用intern()方法是直接将调用该方法的字符串复制一份,再尝试添加到StringTable
中,StringTable
存放在方法区中的常量池,滥用会导致永久代内存溢出。
Vmoptions语句
-Xss栈内存
-Xms堆初始内存
-Xmx堆最大内存
-Xmn新生代内存
-XX:MaxMetaSpaceSize=元空间内存 (JDK1.7+)
-XX:MaxPermSize=永久代内存 (JDK1.6-)
-XX:SurvivorRatio=ratio(默认为8,新生代伊甸园占比8/10)幸存区比例
-XX:+UseAdaptiveSizePolicy 开启幸存区比例的动态变化
-XX:MaxTenuringThreshold=threshold 晋升阈值
-XX:+PrintTenuringDistribution 开启打印晋升详情
-XX:+ScavengeBeforeFullGC 开启在FullGC前MinorGC
-XX:-UseGCOverheadLimit 关闭对98%数据使用垃圾回收而只有少于2%数据时报错
-XX:+PrintStringTableStatistics 开启打印字符串表的统计信息
-XX:+PrintGCDetails -verbose:gc 开启打印垃圾回收的详细信息,当内存不足时,StringTable字符串中没有被引用的常量会被垃圾回收
-XX:StringTableSize=字符串表桶大小 字符串表桶大小越大,存取的速度越快,但占用的空间越多。用于StringTable的性能调优
-XX:+DisableExplicitGC 禁用显式的垃圾回收,因为显式的Full GC回收老年代和新生代占用时间较长,但设置后不会回收直接内存,会导致直接内存占用较大
StringTable性能调优
- 调整
-XX:StringTableSize=字符串表桶大小
- 考虑将大量且重复使用的字符串对象入池intern()
直接内存
正常io操作的byte[]读取是磁盘文件->系统缓冲区->Java缓冲区->Java使用
使用ByteBuffer分配1MB的直接内存实现磁盘文件->直接内存->Java使用
ByteBuffer byteBuffer = ByteBuffer.allocate(1024*1024);
- 常见于NIO操作,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受JVM内存管理
- 直接内存的创建分配与回收释放 底层是通过Unsafe对象方法来处理的,allocateMemory对直接内存的分配,freeMemory对直接内存的回收
- ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存
- 避免性能调优时,禁用显式的垃圾回收,导致设置后不会回收直接内存,会导致直接内存占用较大,需要主动使用Unsafe对象调用freeMemory来释放直接内存
获取Unsafe对象
public static Unsafe getUnsafe(){
try {
Field f = Unsafe.class.getField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
引用类型以及引用队列
强引用
- 根对象直接引用对象A
- 不能够被GC回收
软引用**SoftReference
- 根对象直接引用软引用对象,软引用对象引用对象B
- 在内存不足的情况下会被GC回收(一次垃圾回收Full GC后内存空间还是不足的情况下回收软引用)
- 回收后,可以将软引用对象加入引用队列等待资源释放
弱引用**WeakReference
- 根对象直接引用弱引用对象,弱引用对象引用对象C
- 会被GC回收,第一次GC不会全部回收,回收到够用的情况,Full GC会全部回收
- 回收后,可以将弱引用对象加入引用队列等待资源释放
注:软引用对象与弱引用对象需要在创建时绑定引用队列才能够在回收时自动加入引用队列从而释放资源。
虚引用**PhantomReference
- 根对象直接引用虚引用Cleaner对象,虚引用对象引用ByteBuffer从而访问直接内存
- ByteBuffer会被GC回收
- 回收后,虚引用Cleaner对象必须加入引用队列等待资源释放
引用队列**ReferenceQueue
引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。与软引用、弱引用不同,虚引用必须和引用队列一起使用。
垃圾回收算法
标记清除Mark Sweep
- 优点:回收速度较快
- 缺点:存在内存碎片
标记整理Mark Compact
- 优点:消除内存碎片
- 缺点:整理速度较慢
标记:找到根对象GC Root所指向的存活对象,从而回收其他没有被指向的对象
复制Copy
- 优点:消除内存碎片
- 缺点:需要占用两倍的内存空间
分代回收机制
新生代 | ||
---|---|---|
伊甸园EdenSpace | 幸存区From SurvivorSpace | 幸存区To SurvivorSpace |
老年代 |
---|
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发minor gc,伊甸园和幸存者From存活的对象使用copy复制到to中,存活的对象年龄Age加1,并且交换幸存者From和幸存者To
- minor gc 会引发 stop the world (STW),暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
- 当老年代空间不足,会先尝试触发minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长
- 若触发了full gc后,新创建的对象仍然无法加入新生代或老年代,那么就会报错OM