JVM二 垃圾回收
参考大神的blog:
http://www.cnblogs.com/ityouknow/p/5614961.html
另外参考《深入理解JAVA虚拟机》
默认的内存分配
- 堆区默认最小1/64,最大1/4,老年代:新生代=2,Eden:S0=8
- ratio 堆区空余空间 40% 到70% 都会触发resize
所以JVM优化的第一步就是修改默认的内存设置
回收的过程
- 年轻代的内置增长机制和老年代增长机制可以不同步,比如 NGC 增大,但是OGC 可以不变。
- 第一次Minor GC,年轻代 double,而且每2次Minor都触发double。(差不多是 86% – 92%的比例扩增)
栈中的垃圾回收
jvm 栈中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中。
在Java语言中,GC Roots包括:
- 虚拟机栈中引用的对象。
- 方法区中类静态属性实体引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI引用的对象。
年轻代GC
结构
- Eden : S0 :S1 默认(8:1:1) 可以通过设置-XX:SurvivorRatio 设置比例
- minor GC 针对Eden 和 S(from),复制法GC.Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
触发
- Eden内存不够的时候触发minor GC
- S(to) 被填满之后会触发所有对象到老年代
- Minor GC一定次数的对象也会被分配到 老年代(默认15,可以通XX:MaxTenuringThreshold=15来设置)
垃圾回收器
- 年轻代的垃圾回收器: Serial、Parnew、Parallel Scanvenge
- 老年代的垃圾回收器: Serial Old、CMS、Parallel Old
- 混合带垃圾回收器: G1回收器
并且垃圾回收器的搭配是有限定的,不是任意都可以搭配
Serial收集器
- 分为年轻代就叫 Serial 和老年代的 Serial Old
- 串行回收,高效 稳定但是stop the world较长
- 适合单核CPU,效率高而且稳定,但是STW 较长
- 是Client模式下的默认GC机制
ParNew收集器
- serial 收集器的年轻代多线程版本
- 但是存在线程切换的开销,如果CPU核数不多的情况下,效率还没有Serial高,且不能保证在2个内核的情况下比Serial快
- 因为Parallel Scanvage不能和CMS搭配,所以新生代只有ParNew可以和CMS进行搭配了
Parallel Scanvenge 收集器
- 根据内部算法自动调整内存分配,从而达到吞吐量(工作时间/工作时间+GC时间)最大,默认99%
- 适合不需要和后台交互的系统,从而达到CPU利用最大化
参数: 设置最大停顿时间,设置吞吐量比例
CMS收集器(concurrent Mark Sweep) 并发标记清除
特点:
- 追求的是停顿时间最短
- 初始标记:STW 快速可达根节点对象
- 并发标记:并发客户线程,分配一个线程来tracking GC ROOTS
- 重新标记 并发清除
缺点:
对CPU 资源敏感
在耗时的并发标记和并发清除的时候,都需要单独一个线程来耗时完成,如果只有2个CPU的话,那么需要占用1个CPU来长时间的GC,程序就会慢空间利用率不高
因为是标记清除所以有内存碎片,可以设置几次Full GC的时候压缩内存空间,可能因为有大对象没有足够的连续空间而提前触发full GC无法处理浮动垃圾
也就是并发清除的阶段可能会产生新的垃圾,所以这一阶段产生的垃圾要在下一次GC完成,并且要预留一定的space来存这些对象。如果新增的对象太多,会GC失败,触发Serial Old来重新GC。
JVM优化
- 默认情况下每次FULL GC都会做一次 标记压缩,可以设置参数,多少次FULL GC后压缩一次
- 可以设置在FULL GC之前进行压缩,从而减少FULL GC的频率
G1回收器(Gabage First)
特点:
- 针对多CPU 高性能服务器采用的复杂的回收机制(都是多线程的)
- 同时做到高吞吐 可以设置 STW时间,又保证高效(因为是按照活度来优先回收region)
- G1回收器管理了整个堆,而不是像其他回收器针对年轻代还是老年代管理。
问题:
- 说是老年代用的是标记整理算法,为什么有 Recently Copied old generation?这不是标记复制吗?不是说效率很低的吗?
- 是否支持部分回收? 因为设置了最长时间,可以按照存活比率优先回收 region
G1的新生代回收
当新生代(可能是Eden)区达到一定比例的时候(新生代可能是多个region组成的),并行的做一次MinorGC,多个Region的 Eden 和Survior区(recently copied young generation 新名子),扫完之后,年龄超过15的进入老年代,其余的参与对象复制到一个或者多个 空的region里,成为新的Suvior区,然后清空掉老的Suvior和Eden区。
G1老年代的回收
网上的都看不懂,特点记住
- 分region的,老年代采用标记整理的方式进行,相比CMS,不会有碎片导致大对象引发提前Full GC
- 而且目前也不是非常成熟
- Remember Set 看不懂
JVM调优
在不依赖工具的情况下,调用JVM接口查看jvm参数和使用情况
http://www.ityouknow.com/jvm/2017/09/03/jvm-command.html
注意事项:
jstat -gc 看的S0C 容量是当前容量,和-gccapacity 的S0C不是一个概念, 后者是上限和下限,而前者指的是当前容量,因为当前容量是可以策略调整的。
-gc 里面的 S0U 才是最真实的实时的使用量。可以根据这个实际使用量来衡量设置的阈值是否合适
jstat
查看JVM内存容量分配
./jstat -gccapacity pid
ps -ef|grep java 找到JDK的pid 和路径,然后进入到bin目录下,查看容量大小
查看当前使用情况
./jstat -gc pid times count
对20289 pid进行1秒钟监控一次,共输出5次
./jstat -gc 20289 1000 5
当前总容量下的实际使用率(强调百分比)
jstat -gcutil pid
查看在当前总容量下的实际使用率,当前总容量不一定是设置的最大容量,可以动态调整的
jmap
查看当前堆的参数设置和使用情况
这个是最详细的描述堆区信息的命令,包括GC回收器的类型,堆区默认参数和使用情况
./jmap -heap 28920
jinfo
./jinfo -flags 3181 查看jvm的所有参数
./jinfo pid 查看系统参数 和jvm参数
GC日志计算规则
- 回收器类型 JDK 1.7 默认是 Mark Sweep Compact
- 最大堆区:设置的1500M
- 最小500M, 而默认的新生代 和老年代的比例是2,所以 新生代的大小是500/3=166M
- Eden:S=4,所以S0=166/6=27.75, Eden: 27.75*4=111M, 111 +27.75 +27.75=16
- 都是不含S0区的大小
- GC实际耗时 参考的是一个时间0.0314790秒 解决 real=0.04
- FULL GC会触发 Minor GC OldGC PermGC
虚拟机环境
所以第一行: 113792 –> 13544K (142208)
年轻代GC前大小 –> 年轻代GC后大小 (年轻代容量 不含S0)
(500M/3)1024K=170666K (新生代总大小)
170666/6=28444K(S0区大小)
(170666/6)5=142221( S0+Eden区的大小 也就是显示的 142208K)
113792 –>13544K(483585K) 堆内存GC前实际大小 –> GC后堆内存大小 (堆实际大小 不含S0区)
483585K+28444=512029K /1024=500M
测试环境含FULL GC
因为测试环境用的是 Parallel GC回收器,系统会根据算法分配内存空间,从而获取最大的吞吐量(减少GC时间占用总时间的比率)
GC日志参数设置
可以参照这个设置在 catelina.sh中配置
JAVA_OPTS=”-server -Xms500m -Xmx1500m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:/usr/local/jvmlog/gc.log
-Djava.awt.headless=true
-XX:+PrintGCDateStamps -XX:+PrintGCDetails
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000 -XX:MaxTenuringThreshold=15”
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式 基准时间是tomcat的启动时间)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径