看了一篇大神的文章
http://www.ibm.com/developerworks/cn/java/j-lo-jvm-optimize-experience/index.html
简单记录一下自己的理解。
- 前传
下面这个图是之前出问题时记录下的内存状态,当时不是很理解各个区域的具体含义。
下面 这个图就是永生区的状态
- 开篇
接下来对照前传中的信息,了解了各个区域的含义:
- 祖国花朵的培养
先看看没有人工干预的花朵会是什么状态,使用 JVM 参数-XX:+PrintGCDetails -Xmx20M -Xms20M 运行下面的代码,会看到一系列GC的状态输出(原文的顺序有问题,文字略)
public class PutInEden {
public static void main(String[] args){
byte[] b1,b2,b3,b4;//定义变量
b1=new byte[1024*1024];//分配 1MB 堆空间,考察堆空间的使用情况
b2=new byte[1024*1024];
b3=new byte[1024*1024];
b4=new byte[1024*1024];
}
}
日志输出显示年轻代 Eden 的大小有 5MB 左右。增大分配年轻代空间,使用 JVM 参数-XX:+PrintGCDetails -Xmx20M -Xms20M-Xmn6M 再运行上面的代码,可以发现通过设置一个较大的年轻代预留新对象,设置合理的 Survivor 区并且提供 Survivor 区的使用率,可以将年轻对象保存在年轻代。一般来说,Survivor 区的空间不够,或者占用量达到 50%时,就会使对象进入年老代 (不管它的年龄有多大)。这样优化过的分配方案会降低Full GC的几率,而Full GC 的成本远远高于 Minor GC。
- 栋梁一代也要培养
稍微改一下之前的代码,看看栋梁一带怎么个状态
public class PutInEden2 {
public static void main(String[] args){
byte[] b1,b2,b3;
b1=new byte[1024*512];//分配 0.5MB 堆空间
b2=new byte[1024*1024*4];//分配 4MB 堆空间
b3=new byte[1024*1024*4];
b3=null; //使 b3 可以被回收
b3=new byte[1024*1024*4];//分配 4MB 堆空间
}
}
使用参数-XX:+PrintGCDetails -Xmx1000M -Xms500M -Xmn100M -XX:SurvivorRatio=8 运行上面的代码。输出的日志显示,年轻代分配了 8M,年老代也分配了 8M。我们可以尝试加上-XX:TargetSurvivorRatio=90 参数,这样可以提高 from 区的利用率,使 from 区使用到 90%时,再将对象送入年老代。如果将 SurvivorRatio 设置为 2, b1 对象将预存在年轻代。
- 让大块头都去找老一代混吧
大部分情况下都会选择将对象分配在年轻代。但是,对于占用内存较多的大对象而言,它的选择可能就不是这样的。因为大对象出现在年轻代很可能扰乱年轻代 GC,并破坏年轻代原有的对象结构。因为尝试在年轻代分配大对象,很可能导致空间不足,为了有足够的空间容纳大对象,JVM 不得不将年轻代中的年轻对象挪到年老代。因为大对象占用空间多,所以可能需要移动大量小的年轻对象进入年老代,这对 GC 相当不利。
所以我们需要上来就将大块头送到老年代去混。而且还要注意一点
可以使用参数-XX:PetenureSizeThreshold 设置大对象直接进入年老代的阈值。当对象的大小超过这个值时,将直接在年老代分配。参数-XX:PetenureSizeThreshold 只对串行收集器和年轻代并行收集器有效,并行回收收集器不识别这个参数
- 年龄带是以什么为准来划分的呢
虚拟机为每个对象都维护一个年龄。如果对象在 Eden 区,经过一次 GC 后依然存活,则被移动到 Survivor 区中,对象年龄加 1。以后,如果对象每经过一次 GC 依然存活,则年龄再加 1。当对象年龄达到阈值时,就移入年老代,成为老年对象。这个阈值的最大值可以通过参数-XX:MaxTenuringThreshold 来设置,默认值是 15。虽然-XX:MaxTenuringThreshold 的值可能是 15 或者更大,但这不意味着新对象非要达到这个年龄才能进入年老代。事实上,对象实际进入年老代的年龄是虚拟机在运行时根据内存使用情况动态计算的,这个参数指定的是阈值年龄的最大值。即,实际晋升年老代年龄等于动态计算所得的年龄与-XX:MaxTenuringThreshold 中较小的那个。
- 堆的大小能不能再好一点
稳定的堆大小对垃圾回收是有利的。获得一个稳定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一样。如果这样设置,系统在运行时堆大小理论上是恒定的,稳定的堆空间可以减少 GC 的次数。因此,很多服务端应用都会将最大堆和最小堆设置为相同的数值。但是,一个不稳定的堆并非毫无用处。稳定的堆大小虽然可以减少 GC 次数,但同时也增加了每次 GC 的时间。让堆大小在一个区间中震荡,在系统不需要使用大内存时,压缩堆空间,使 GC 应对一个较小的堆,可以加快单次 GC 的速度。基于这样的考虑,JVM 还提供了两个参数用于压缩和扩展堆空间。
-XX:MinHeapFreeRatio 参数用来设置堆空间最小空闲比例,默认值是 40。当堆空间的空闲内存小于这个数值时,JVM 便会扩展堆空间。
-XX:MaxHeapFreeRatio 参数用来设置堆空间最大空闲比例,默认值是 70。当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆。
当-Xmx 和-Xms 相等时,-XX:MinHeapFreeRatio 和-XX:MaxHeapFreeRatio 两个参数无效
- 吞吐量也是可以改良的
吞吐量优先的方案将会尽可能减少系统执行垃圾回收的总时间,故可以考虑关注系统吞吐量的并行回收收集器。在拥有高性能的计算机上,进行吞吐量优先优化,可以使用参数:
java –Xmx3800m –Xms3800m –Xmn2G –Xss128k –XX:+UseParallelGC
–XX:ParallelGC-Threads=20 –XX:+UseParallelOldGC
-Xss128k:减少线程栈的大小,这样可以使剩余的系统内存支持更多的线程;
-Xmn2g:设置年轻代区域大小为 2GB;
–XX:+UseParallelGC:年轻代使用并行垃圾回收收集器。这是一个关注吞吐量的收集器,可以尽可能地减少 GC 时间。
–XX:ParallelGC-Threads:设置用于垃圾回收的线程数,通常情况下,可以设置和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的;
–XX:+UseParallelOldGC:设置年老代使用并行回收收集器。
- 回收器也是可选的
为降低应用软件的垃圾回收时的停顿,首先考虑的是使用关注系统停顿的 CMS 回收器。另外还有G1(Garbage First)收集器,G1 收集器的目标是作为一款服务器的垃圾收集器,因此,它在吞吐量和停顿控制上,预期要优于 CMS 收集器。
java –Xmx3550m –Xms3550m –Xmn2g –Xss128k –XX:ParallelGCThreads=20
–XX:+UseConcMarkSweepGC –XX:+UseParNewGC –XX:+SurvivorRatio=8 –XX:TargetSurvivorRatio=90
–XX:MaxTenuringThreshold=31
–XX:ParallelGCThreads=20:设置 20 个线程进行垃圾回收;
–XX:+UseParNewGC:年轻代使用并行回收器;
–XX:+UseConcMarkSweepGC:年老代使用 CMS 收集器降低停顿;
–XX:+SurvivorRatio:设置 Eden 区和 Survivor 区的比例为 8:1。稍大的 Survivor 空间可以提高在年轻代回收生命周期较短的对象的可能性,如果 Survivor 不够大,一些短命的对象可能直接进入年老代,这对系统来说是不利的。
–XX:TargetSurvivorRatio=90:设置 Survivor 区的可使用率。这里设置为 90%,则允许 90%的 Survivor 空间被使用。默认值是 50%。故该设置提高了 Survivor 区的使用率。当存放的对象超过这个百分比,则对象会向年老代压缩。因此,这个选项更有助于将对象留在年轻代。
–XX:MaxTenuringThreshold:设置年轻对象晋升到年老代的年龄。默认值是 15 次,即对象经过 15 次 Minor GC 依然存活,则进入年老代。这里设置为 31,目的是让对象尽可能地保存在年轻代区域。
与 CMS 收集器相比,G1 收集器是基于标记-压缩算法的。因此,它不会产生空间碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工作。G1 收集器还可以进行非常精确的停顿控制。它可以让开发人员指定当停顿时长为 M 时,垃圾回收时间不超过 N。使用参数-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 来启用 G1 回收器,设置 G1 回收器的目标停顿时间:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200。
GC 相关参数总结
1. 与串行回收器相关的参数
-XX:+UseSerialGC:在新生代和老年代使用串行回收器。
-XX:+SuivivorRatio:设置 eden 区大小和 survivor 区大小的比例。
-XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。
-XX:MaxTenuringThreshold:设置对象进入老年代的年龄的最大值。每一次 Minor GC 后,对象年龄就加 1。任何大于这个年龄的对象,一定会进入老年代。
2. 与并行 GC 相关的参数
-XX:+UseParNewGC: 在新生代使用并行收集器。
-XX:+UseParallelOldGC: 老年代使用并行回收收集器。
-XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。
-XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。
-XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。
-XX:+UseAdaptiveSizePolicy:打开自适应 GC 策略。在这种模式下,新生代的大小,eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。
3. 与 CMS 回收器相关的参数
-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器。
-XX:+ParallelCMSThreads: 设定 CMS 的线程数量。
-XX:+CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被使用多少后触发,默认为 68%。
-XX:+UseFullGCsBeforeCompaction:设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收。
-XX:+CMSParallelRemarkEndable:启用并行重标记。
-XX:CMSInitatingPermOccupancyFraction:当永久区占用率达到这一百分比后,启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
-XX:UseCMSInitatingOccupancyOnly:表示只在到达阈值的时候,才进行 CMS 回收。
-XX:+CMSIncrementalMode:使用增量模式,比较适合单 CPU。
4. 与 G1 回收器相关的参数
-XX:+UseG1GC:使用 G1 回收器。
-XX:+UnlockExperimentalVMOptions:允许使用实验性参数。
-XX:+MaxGCPauseMills:设置最大垃圾收集停顿时间。
-XX:+GCPauseIntervalMills:设置停顿间隔时间。
5. 其他参数
-XX:+DisableExplicitGC: 禁用显示 GC。