JVM总结
java源文件 -> 编译为class文件 -> 加载到jvm(类加载)
类加载过程
1.装载
- 通过全路径找到class文件,ClassLoader.find(name)
- 类加载的双亲委派原则:当一个类加载器要加载一个类时,它会先委托自己的父加载器来加载,只有当父加载器无法加载类时,才会自己去加载。
2.链接
- 验证:验证类的格式正确性
- 准备:静态变量初始化为默认值(例如int类型是0)
- 解析:类中的符号引用转换为直接引用
3.初始化
- 静态变量赋值
- 执行静态代码块
JVM的运行时数据区
1.方法区
- 类信息
- 常量、静态变量
JDK1.7:PermSpace 永久代
JDK1.8:MetaSpace 元空间
2.堆
-
对象存储在堆上
-
堆可分为年轻代(Eden,S0,S1)和老年代。
3.虚拟机栈
- 方法执行:栈帧的压入和弹出
- 栈帧:
- 局部变量表
- 操作数栈
- 动态链接
- 方法返回地址
4.本地方法栈
- 与虚拟机栈类似,native方法
5.程序计数器
- 记录下一条指令的地址
逃逸分析
-
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
-
在java 虚拟机中,对象是在java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需要在堆上分配内存,也无需进行来集回收了。
Java对象内存布局
一个Java对象的内存布局包括:对象头、实例数据、对齐填充
-
可以根据这个结构估算出一个java对象的大小
-
对象头中的MarkWord包含三类信息:
- 对象的hashCode:当调用了hashCode方法后会写入到此处
- GC年龄:存储大小4位,故最大值为15,所以GC的年龄阈值MaxTenuringThreshold参数最大值为15
- 锁标识:synchronized多线程同步时会使用到这部分信息
-
Class Pointer:指向方法区中Class信息的指针
-
对齐填充是为了提升性能,假设对象是不等长的,那么为了获取一个完整的对象,就必须一个字节一个字节地去读,直到读到结束符,但是如果8字节对齐后,获取对象就可以以8个字节为单位进行读取,快速获取到一个对象,是一种以空间换时间的设计方案。
GC
-
Minor GC 或 Yong GC(年轻代):当Eden区或S区不够用了
-
Major GC(老年代):当老年代不够用了,一般会伴随Minor GC
-
Full GC(年轻代 + 老年代)
-
方法区GC,GC日志中通常会显示为【Full GC(Metadata GC Threshold)xxxxx】
Metaspace
- metaspace使用的是本地内存
- metaspace的初始大小并不等于设置的MetaspaceSize参数。随着类的加载,metaspace会不断进行扩容。
- metaspace的最大值默认是没有上限的,除非配置MaxMetaspaceSize
- metaspace发生GC的时机:
- metaspace在没有更多的内存空间的时候。
- JVM内部有一个叫做_capacity_until_GC的变量,一旦metaspace使用的空间超过这个变量的值,就会对metaspace进行回收。这个变量的初始值为MetaspaceSize,但是会自动的在 MetaspaceSize和MaxMetaspaceSize之间进行调整。
垃圾回收
如何判断哪些对象需要回收
- 引用计数(存在循环引用问题)
- 可达性分析(GC Root的选择)
回收算法
- 标记清除(需要遍历,效率较低;内存碎片)
- 标记整理(效率较低,解决了内存碎片)
- 复制算法(在垃圾对象多的情况下,效率较高。内存使用率较低)
垃圾收集器
垃圾收集器是对回收算法的落地
新生代:复制算法
老年代:标记清除/整理
Serial,Seerial Old:单线程
ParNew,ParallelScavenge(更加关注吞吐量)、ParallelOld:多线程
CMS:用户线程和垃圾回收线程并发执行,较短的停顿时间
G1:并发执行,可以设置pause time
垃圾收集器的选择
GC收集器:停顿时间和吞吐量
-
停顿时间 = 每次执行垃圾回收造成用户线程停顿的平均时间
-
吞吐量 = 用户线程执行时间 /(用户线程执行时间 + GC时间)
停顿时间小:CMS、G1【set pause time】
吞吐量优先:Parallel Scanvent、 Parallel Old
串行收集:Serial、Serial Old,内存比较小、嵌入式设备
JVM参数
-XX参数
- 布尔型:-XX:[+/-]name 启动或者停止
- 非布尔型:-XX:name=value
其他参数[-XX参数的其他写法]
- -Xms100M 相当于 -XX:InitialHeapSize=100M
- -Xmx100M 相当于 -XX:MaxHeapSize=100M
命令
- jps:查看当前java进程
- jinfo:查看或者修改jvm参数
- jstat: 查看class装载或者gc信息
- jstack:查看线程信息(排查死锁)
- jmap:查看堆信息或者生成堆的dump文件
查看堆的dump文件的工具
- jconsole
- jvisualvm
- arthas:阿里开源的java诊断工具
- MAT(Memory Analyzer Tool)打开dump文件,详细的分析报告,可能存在问题的代码
查看GC日志的工具
- gcviewer
JVM调优 - Why?
- CPU飙升
- 内存空间不够用
- GC次数太多,影响用户线程执行效率
- 提高吞吐量、较少停顿时间
JVM调优-排查堆的使用情况
分析dump文件,排查哪个对象占用内存空间大
JVM调优-排查垃圾回收情况
- 使用工具查看GC日志
- 如何通过选择垃圾收集器优化:调整JVM配置后,解析GC日志。关注指标: 吞吐量、停顿时间、GC次数。
使用G1垃圾回收器的调优例子
配置 | 吞吐量 | 平均停顿时间 | GC次数 | 备注 |
---|---|---|---|---|
UseG1GC | 92.89% | 0.0107s | 21 | |
堆内存增加一倍 | 95.68% | 0.0182s | 3 | 吞吐量增大,GC次数较少。但是停顿时间变大,原因:更多的内存意味着可以积累的垃圾更多,一次回收的时间会拉长 |
设置停顿时间为15ms | 95.02% | 0.0089s | 14 | 停顿时间减少了,但GC次数变大,原因:停顿时间减少说明一次GC的标记对象减少了,所以次数多了 |
- G1可以设置启动GC的堆存在占比,默认为45,即内存使用率为45%时启动触发GC
- 使用G1时,不要设置年轻代大小,如果设置了大小则会破环停顿时间的目标。因为G1会自适应的调整年轻代的大小。
- 增加G1垃圾回收标记线程数量
- 等等,可参考官网建议
G1和CMS的区别
CMS:标记清除
G1:
- 标记整理
- G1把连续的Java堆划分为多个大小相等的Region,每一个Region都可以作为年轻代的Eden空间、Survivor空间,或者老年代空间。
- 优先收集垃圾比较多的区域
- CSet
- 硬件要求:多核、大内存(至少内存6G内存)
- 等等