JVM:垃圾收集器与内存分配策略(下)

HotSpot算法实现

枚举根节点:从GC Roots节点中找出引用链的操作。
GC Roots对象主要在全局性引用(常量/类静态属性)与执行上下文(栈帧中的本地变量表)中,虚拟机不会逐个检查这些地方,HotSpot使用OopMap数据结构来记录。当类加载完成时,OopMap中就记录下对象内为引用的位置,和栈、寄存器中引用的位置。当GC扫描时就可以直接得知引用信息。

GC Roots.jpg

Stop The World:在可达性分析期间,需要这期间的一致性——不能一边分析,对象的引用关系还一直变化,所以GC时必须停顿所有的Java执行线程。

安全点:为了避免对每条指令都生成OopMap,而选择的记录信息的特定的位置。只有在安全点,程序才会停下来GC。安全点的选定标准:是否具有让程序长时间执行的特征,例如,方法调用、循环跳转、异常跳转。
让所有线程都到最近的安全点再停顿的方法:

  • 抢先式中断:先中断所有线程,再让不在安全点的线程跑到安全点中断。(几乎不用)
  • 主动式中断:设置标志,各线程主动轮询标志,发现中断标志为真时就自己中断挂起。其中轮询标志的地方和安全点重合。

安全区域:针对不执行的线程(sleep或blocked状态)设置。当线程进入安全区时,就标识自己,GC时就不用管这些线程。当线程要离开安全区时,要先检查系统是否完成根节点枚举(或GC),要完成了才能安全离开安全区。

安全区.jpg

垃圾收集器

HotSpot的垃圾收集器.jpg

新生代垃圾收集器

image.png
  • 对于单CPU环境,ParNew收集器不如Serial收集器效果好。其默认开启的GC线程数=CPU数量,也可以使用-XX:ParallelGCThreads设置线程数。
  • 其他收集器专注缩短停顿用户线程的时间,Parallel Scavenge专注于控制吞吐量。吞吐量=CPU执行用户代码的时间/(运行用户代码时间+垃圾收集时间)。高吞吐量即意味着可以高效利用CPU,尽快执行完客户代码。
    -XX:MaxGCPauseMillis:设定GC停顿的时间
    -XX:GCTimeRatio:设定垃圾收集时间占总时间的比率,即吞吐量的倒数。
    -XX:+UseAdaptiveSizePolicy:打开此参数后,虚拟机动态调整新生代大小、Eden与Survivor区的比例、晋升老年代对象大小等细节的参数。

老年代垃圾收集器

image.png

CMS收集器(Concurrent Mark Sweep)

  • 以获取最短停顿时间为目标的收集器
  • 适用于追求停顿时间最短的B/S系统服务端
  • 基于标记-清楚算法


    CMS运行图.jpg
  1. 初始标记(CMS initial mark)
    STOP THE WORLD,标记GC Roots能直接关联的对象,速度最快
  2. 并发标记(CMS concurrent mark)
    进行GC Roots Tracing
  3. 重新标记(CMS remark)
    STOP THE WORLD,修正在并发标记期间有变动的对象标记记录
  4. 并发清除(CMS concurrent sweep)
    回收对象

缺点

  • 对CPU资源敏感
    在并发标记和并发清理,会占用CPU资源,导致程序变慢,总吞吐量降低。CMS默认启动的回收线程数=(CPU数量+3)/4,当CPU较少时,会对程序产生很大影响。
  • 无法处理浮动垃圾(Floating Garbage)
    浮动垃圾指,在并发清理阶段产生的新的需要回收的对象。使用CMS收集器,需要预留一部分内存给用户线程使用,如果在并发清理期间,预留内存无法满足程序需要,就会出现"Concurrent Mode Failure",然后临时启用Serial Old收集器来重新进行老年代的垃圾收集,导致停顿时间变长。可以使用参数-XX:CMSInitiatingOccupancyFraction来设置CMS触发的百分比。
  • 空间碎片
    CMS使用标记-清理算法,会产生大量空间碎片。空间碎片过多时会影响大对象的分配。使用-XX:+UseCMSCompactAtFullCollection开关参数,在要进行Full GC前,开启内存碎片的合并整理过程。但此操作不能并发,会导致停顿时间增加。-XX:CMSFullGCsBeforeCompaction则设置,在执行n次不压缩的Full GC后,执行一次碎片整理的Full GC。

G1收集器(Garbage-first)

适用于服务器端。
优点:

  • 并行与并发
    与CMS相似。
  • 分代收集
    可以不与其他收集器配合,独立管理整个GC堆,并在不同的代里使用不同的收集算法。
  • 空间整合
    G1运作期间不会产生内存空间碎片,有利于程序长时间运行,不会因为没有足够的空间分配给大对象而提前触发GC。
  • 可预测停顿
    可以建立可预测的停顿时间模型,让使用者明确指定在一个长度为M毫秒的时间片段中,消耗在垃圾收集上的时间不超过N毫秒。
    G1将Java堆划分为多个大小相等的独立区域(Region),虽然也分新生代、老年代,但他们都是一部分Region的集合,不存在物理隔离。G1根据各个Region中垃圾堆积的价值大小(回收后可以获得的空间大小和回收所需时间的经验值),在后台维护一个优先列表,每次根据用户允许的收集时间,优先回收价值最大的Region。其难点在于,Region不是孤立的,一个对象会被其他Region的对象引用。在做可达性分析寻找可回收对象时,需要扫描整个Java堆才能保证准确性。G1使用Remembered Set来避免全堆扫描。每个Region对应一个Remembered Set,当程序对引用类型的数据进行写操作时,虚拟机产生一个Write Barrier暂时中断操作,检查引用的对象是否处于不同的Region,如果是的话,就通过CardTable把相关应用信息记录到被引用对象所属的Region的Remembered Set中。进行内存回收的时候,在GC根节点枚举范围加上Remembered Set。


    G1收集器运行图.jpg
  1. 初始标记
  2. 并发标记
  3. 最终标记
    STOP THE WORLD,可并发执行最终标记。修正并发标记期间有变动的对象记录,虚拟机将这些变化记录在线程Remembered Set Logs里面,再合并到Remembered Set中。
  4. 筛选回收
    STOP THE WORLD,可并发执行。对各个Region的回收价值和成本进行排序,根据用户的需求来指定回收计划。

GC日志

// 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
[GC (Allocation Failure) [PSYoungGen: 7128K->616K(9216K)] 7128K->6768K(19456K), 0.0057651 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 616K->0K(9216K)] [ParOldGen: 6152K->6658K(10240K)] 6768K->6658K(19456K), [Metaspace: 2542K->2542K(1056768K)], 0.0066751 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
  • GC/FULL GC:说明垃圾回收的停顿类型,full gc是发生了stop the world的。
  • PSYoungGen/ParOldGen:发生GC的区域。
    PSYoungGen:Parallel Scavenge收集器新生代
    DefNew:Default New Generation,使用Serial收集器中的新生代
    ParNew:ParNew收集器的新生代
    ParOldGen:Parallel Old 的老年代
  • 7128K->616K(9216K):GC前该内存区域已使用容量->GC后已使用容量(总容量)
  • 7128K->6768K(19456K)(方括号外):GC前java堆已使用容量->GC后已使用容量(java堆总容量)
  • 0.0057651 secs:GC所占用时间(秒)。
  • [Times: user=0.00 sys=0.00, real=0.01 secs] :【用户态消耗CPU时间,内核态消耗CPU时间,墙钟时间(Wall Clock Time)】。墙钟时间 = 非运算等待耗时(磁盘IO/线程阻塞等待时间) + CPU时间。

内存分配与回收策略

  • 优先在Eden上分配
    优先在Eden上分配,Eden空间不够时,发起Minor GC,将存活对象移到Survivor中去。
  • 大对象直接进入老年代
    大对象,即需要大量连续内存空间的Java对象(如很长的字符串、数组)。通过-XX:PretenureSizeThreshold参数,大于这个值的对象都会直接在老年代中进行分配。
  • 长期存活对象进入老年代
    计算对象的对象年龄计数器,新生代的对象每熬过一次Minor GC,年龄就加1。年龄超过-XX:MaxTenuringThreshold参数的对象,就会被晋升到老年代。
  • 动态对象年龄判断
    在Survivor空间中,相同年龄所有对象的大小总和大于Survivor空间的一般时,大于这个年龄的对象就会被晋升到老年代。
  • 空间分配担保
    Minor GC前,虚拟机先检查老年代的最大可用的连续空间,是否大于新生代所有对象总空间。如果不成立,就查看HandlePromotionFailure设置的值是否允许担保失败。如果允许,检查老年代最大可用的连续空间,是否大于历次晋升到老年代的对象的平均大小。如果大于,就尝试进行Minor GC,否则就进行Full GC。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容