概述
本文主要介紹JVM以及JVM优化和FULL GC 的处理.
什么是JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
JVM HEAP分区
JVM HEAP可分为三大区新生代- young generation、老年代 - old generation、永久代 - Permanent generation(也叫方法区).
young generation 又分为 Eden区和两个survivor区(FROM SURVIVOR 和 TO SURVIVOR),两个survivor区存放每次垃圾回收后存活的对象.
young generation主要用于存放new出来的对象,长时间未被GC回收的会进入到survivor区,如果survivor区对象长时间未被回收则会被提升到老年区,老年区一般存放比较大的对象,至于Permanent generation 则存放静态对象例如static。
permanent generation管理class文件、静态对象、属性等。
JVM 垃圾回收机制
JVM垃圾回收机制采用“分代收集”:新生代采用复制算法,老年代采用标记清理算法。
JVM 复制(Copying)算法
将内存平均分成A、B两块,算法过程:
- 新生对象被分配到A块中未使用的内存当中。当A块的内存用完了, 把A块的存活对象对象复制到B块。
- 清理A块所有对象。
- 新生对象被分配的B块中未使用的内存当中。当B块的内存用完了, 把B块的存活对象对象复制到A块。
- 清理B块所有对象。
- goto step 1
算法优化:使用Eden/S0/S1三个分区
如上述过程,如果平均分成A/B块太浪费内存,采用Eden/S0/S1三个区更合理,空间比例为Eden:S0:S1==8:1:1,有效内存(即可分配新生对象的内存)是总内存的9/10。
算法过程:
- Eden+S0可分配新生对象;
- 对Eden+S0进行垃圾收集,存活对象复制到S1。清理Eden+S0。一次新生代GC结束。
- Eden+S1可分配新生对象;
- 对Eden+S1进行垃圾收集,存活对象复制到S0。清理Eden+S1。二次新生代GC结束。
- goto step 1
默认Eden:S0:S1=8:1:1,因此,新生代中可以使用的内存空间大小占用新生代的9/10。
为什么不直接分成两个区,一个区占9/10,另一个区占1/10,这样做的原因大概有以下几种
1.S0与S1的区间明显较小,有效新生代空间为Eden+S0/S1,因此有效空间就大,增加了内存使用率.
2.有利于对象代的计算,当一个对象在S0/S1中达到设置的XX:MaxTenuringThreshold值后,会将其分到老年代中,设想一下,如果没有S0/S1,直接分成两个区,该如何计算对象经过了多少次GC还没被释放,你可能会说,在对象里加一个计数器记录经过的GC次数,或者存在一张映射表记录对象和GC次数的关系,是的,可以,但是这样的话,会扫描整个新生代中的对象, 有了S0/S1我们就可以只扫描S0/S1区了
JVM调优配置及参数说明
CMD java
//1 非标准参数配置
-Xms1024m
//指定jvm堆的初始大小,默认为物理内存的1/64,最小为1M;可以指定单位,比如k、m,若不指定,则默认为字节。
-Xmx1024m
//指定jvm堆的最大值,默认为物理内存的1/4或者1G,最小为2M;单位与-Xms一致。
-Xmn480m
//JVM中年轻态的大小 年轻态比老年态为1/1 持久态最好为64M 老年态为480 FULLCG次数会较少 但是每次FULLCG时间会较长
-Xss256K
//单个线程栈的大小 一般默认512k
//2 非stable参数
-XX:PermSize=64M
//持久态的大小 JDK8是另一个参数设置持久态
-XX:MaxPermSize=64M
//设置持久代最大值为100m,默认为xms的1/8。MaxPermSize过小会导致:java.lang.OutOfMemoryError: PermGen space
//32位Linux和64位Linux好像不一样,64位系统似乎只要配置MaxTenuringThreshold参数。
#-XX:MaxTenuringThreshold=20
//MaxTenuringThreshold=0就是去掉了救助空间 MaxTenuringThreshold去掉,这样即没有暂停又不会有promotoin failed,而且更重要的是,年老代和永久代上升非常慢(因为好多对象到不了年老代就被回收了),所以CMS执行频率非常低,好几个小时才执行一次
-XX:SurvivorRatio=4
//Eden区与Survivor区的大小比值4/1
/**适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。
响应时间优先的应用:年轻代大小选择
尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
响应时间优先的应用:年老代大小选择
年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
**/
-XX:+UseConcMarkSweepGC
//对老生代采用并发标记交换算法进行GC
-XX:+CMSParallelRemarkEnabled
// 降低标记停顿
-XX:CMSFullGCsBeforeCompaction=0
//由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此参数设置运行n次FullGC以后对内存空间进行压缩、整理。
-XX:CMSInitiatingOccupancyFraction=70
//表示年老代空间到70%时就开始执行CMS,确保年老代有足够的空间接纳来自年轻代的对象。 满足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn就不会出现promotion 内存太低不适应
-XX:+UseCMSCompactAtFullCollection
//使用并发收集器时,开启对年老代的压缩,failed
-XX:+DisableExplicitGC
//禁止System.gc(),免得程序员误调用gc方法影响性能;
-XX:+UseParNewGC
//对年轻代采用多线程并行回收,这样收得快;
//3 日志打印信息
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/aaa/dump
-Xloggc:/usr/local/mylog/heap_trace.txt
-XX:+PrintGCTimeStamps
-XX:+PrintGC
-XX:+PrintHeapAtGC
-XX:+PrintGCDetails
-Duser.timezone=Asia/Shanghai
-jar yourjarName.jar
JVM 如何测试调优后的性能
对于linux可以使用TOP命令查看内存和CPU占用,在服务高负荷状态下,尽量出发FULL GC 查看GC日志,看new generation和old generation的GC次数和GC后的结果是否达到预期,来调试参数的配比.
jmap -dump:format=b,file=heap.bin [pid]
分析工具可以使用MAT.
总结
一盏灯, 一片昏黄; 一简书, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。