JVM GC

分代GC

JVM的GC机制叫做分代GC(generational GC),把内存分为两种:新生代(Young Generation)和老年代(Old Generation)。这种GC机制是基于这么一个现象:内存中的大部分对象的生命周期都很短,可能只存在几十毫秒,其他少部分对象的生命周期则很长,比如几分钟

当一个对象最开始被分配到堆上的时候,会把它放到young generation上。当这个对象在Young Generation上,经过若干次GC之后,如果依然alive,就会把它放到Old Generation。在Young Generation上的GC称为minor gc,在Old Generation上GC称为full gc。


jvm.png

Young Generation

在Young Genration内部,又分为3个区域,一个eden区,两个survivor区,分别称作survivor0和survivor1。

  1. 每次新创建的对象都会存在于eden区,然后经过一次gc之后,还存活的对象会copy到survivor0区,eden区清空。
  1. 这样,等到下次gc开始的时候,survivor1保持空,eden区存放的是从上次GC开始到这次GC这段时间创建的新的对象,survivor0存放的是上次GC中survive的对象。那么这次GC的时候,会在eden和survivor0中,把那些alive的对象都copy到survivor1,eden和survivor0清空。
  2. 这样循环往复。
  3. 经过若干次(Hotspout是15次,可以用-XX:MaxTenuringThreshold控制)之后,依然存活的对象copy到Old Generation。

从上面的gc过程可以看出:

  1. survivor0和survivor1总有一个会保持为空
  1. 每次GC,并不是把无用的对象清理掉,而是把alive的对象copy到survivor0或者survivor1。之所以要强调这一点,是因为这样的做法保证了Young Generation中的可用空间都是连续的。
  2. GC的时候是stop-the-world的,但是时间非常短。在生产环境中,一般不推荐超过512M,这样即使在最坏的情况下GC时间也不会超过几百毫秒

Old Generation

对于old generation中,GC算法就不会像young generation那样简单粗暴了。通常使用CMS GC算法,也就是Concurrent Mark-Sweep GC。
CMS GC有以下几个阶段

  1. initial-mark (stops the world). In this phase, the CMS collector places a mark on the root objects. A root object is something directly referenced from a live Thread – for example, the local variables in use by that thread. This phase is short because the number of roots is very small.
  1. concurrent-mark (concurrent). The collector now follows every pointer starting from the root objects until it has marked all live objects in the system.
  2. remark (stops the world). Since objects might have had references changed, and new objects might have been created during concurrent-mark, we need to go back and take those into account in this phase. This is short because a special data structure allows us to only inspect those objects that were modified during the prior phase.
  3. concurrent-sweep (concurrent). Now, we proceed through all objects in the heap. Any object without a mark is collected and considered free space. New objects allocated during this time are marked as they are created so that they aren’t accidentally collected.

The important things to note here are:

  1. The stop-the-world phases are made to be very short. The long work of scanning the whole heap and sweeping up the dead objects happens concurrently.
  1. This collector does not relocate the live objects, so free space can be spread in different chunks throughout the heap. We’ll come back to this later!

Stop The World

不管选择哪种GC算法,Stop-The-World都是不可避免的。Stop-The-World意味着从应用中停下来并进入到GC执行过程中去。一旦Stop-The-World发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。GC调优通常就是为了改善Stop-The-World的时间。

JVM 配置调优

调优目标

GC的时候会导致Stop The World,如果JVM频繁GC,会严重影响系统的性能。通过对JVM的参数进行调优,我们期望达到以下目

  1. GC的时间足够短
  1. GC的次数足够少
  2. 发生Full GC的周期足够长

前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

对于Full GC,我们要尽可能地避免。一般造成Full GC的原因有以下几点

  1. 老年代空间不足
  2. CMS GC的时候出现promotion failure和concurrent mode failure。

promotion failure是在进行Minor GC的时候,survivor space放不下,对象只能放入老年代,而此时老年代也放不下。
concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。

JVM配置

为了达到上面的目的,你需要做的事情有:

1、减少使用全局变量和大对象
2、调整新生代的大小到最合适
3、设置老年代的大小为最合适
4、选择合适的GC收集器

对应的JVM的配置参数有

参数 作用
-Xms 设置JVM的初始内存
-Xmx 设置JVM的最大可用内存
-Xmn1024m 设置年轻代的大小
-XX:SurvivorRatio 设置年轻代中Eden区与Survivor区的大小比值。如果设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:NewRatio 设置年轻代(包括Eden和两个Survivor区)与年老代的比值。如果设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:NewSize 和 -XX:MaxNewSize 设置年轻代大小
-XX:MaxPermSize 设置持久代大小
-XX:MaxTenuringThreshold 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率

有三个参数都可以设置年轻代大小

  1. -XX:NewSize=1024m和-XX:MaxNewSize=1024m;
  2. -Xmn1024m;
  3. -XX:NewRatio=2; (假设Heap总共是3G)
    经过实践,这些参数优先级如下
    最高优先级: -XX:NewSize=1024m和-XX:MaxNewSize=1024m
    次高优先级: -Xmn1024m (默认等效效果是:-XX:NewSize==-XX:MaxNewSize==1024m)
    最低优先级:-XX:NewRatio=2

对于GC收集器,JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。

参数 作用
-XX:+UseParallelGC 配置年轻代的垃圾收集方式为并行收集
-XX:+UseParallelOldGC 配置年老代垃圾收集方式为并行收集
-XX:ParallelGCThreads=20 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等
-XX:+UseConcMarkSweepGC 设置年老代为并发收集
-XX:+UseParNewGC 设置年轻代为并发收集
-XX:+UseCMSCompactAtFullCollection 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。打开对年老代的压缩。可能会影响性能,但是可以消除碎片
XX:+PrintGCDetails XX:+PrintGCTimeStamps Xloggc:/usr/aaa/dump/heap_trace.txt 垃圾回收统计信息

调优总结

  1. 年轻代大小选择
    响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
    吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

  2. 年老代大小选择
    响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
    并发垃圾收集信息
    持久代并发收集次数
    传统GC信息
    花在年轻代和年老代回收上的时间比例
    减少年轻代和年老代花费的时间,一般会提高应用的效率
    吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

  3. 较小堆引起的碎片问题
    因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
    -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
    -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

Reference

  1. http://unixboy.iteye.com/blog/174173/
  2. https://segmentfault.com/a/1190000004233812
  3. https://blog.cloudera.com/blog/2011/02/avoiding-full-gcs-in-hbase-with-memstore-local-allocation-buffers-part-1/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,488评论 3 83
  • 一. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对...
    Stan_Z阅读 1,903评论 0 25
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,927评论 2 31
  • 转载blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile阅读 5,338评论 1 56
  • 0. JVM内存组成 JVM内存主要由两部分组成:a.线程私有内存区域;b.线程公共内存区域。 线程公用的内存区域...
    millions_chan阅读 2,791评论 0 9