JVM(三)

到底是单线程垃圾回收好,还是多线程垃圾回收好

前面我知道,新生代的serial垃圾回收使用单线程,而parnew垃圾回收使用多线程.

首先我知道系统系统启动分为服务端模式和客户端模式,我们使用-server是指就是服务端,使用-client就是客户端,他们的区别就是如果在linux服务器上就是应该是服务端,如果是windows上的客户端程序就是客户端模式,

一般服务端模式就是一些,网站系统,电商系统,app后台等等,一般都是多CPU,因此使用parnew垃圾回收可以有效的利用多核,提高性能

但是如果我们在使用单线程的垃圾回收机制,不能充分利用多CPU,性能不是很好,

那么如果是一个java程序是一个客户端,还单线程的,但是要要用多线程的parnew线程去处理,会导致一个cpu,同时处理多个线程,导致频繁的上下文切换,反而会导致系统性能变差

因此不通的场景使用使用不通的垃圾回收器即可,运行在windows上的客户端,使用serial垃圾回收器,单CPU单线程垃圾回收即可,而服务端多核CPU,使用parnew收集即可,

JVM中的CMS垃圾回收机制

public class kafka2 {
private static ReplicaManager replicaManager = new ReplicaManager();
}
public class ReplicaManager {
private ReplicaFetcher replicaFetcher = new ReplicaFetcher();
}

CMS垃圾回收分为四个阶段如下

初始化标记

并发标记

重新标价

并发清除

初始化标记,仅仅会通过replicaManager这个静态变量代表的GC Roots,去标记他直接应用的对象ReplicaManager,这就是初始化标记,并且此时系统会停止工作如下图,工作比较简单,速度很快

并发标记,就是进行GC Roots追踪,就比如,replicaFetcer实例对象之类的老年代里面的对象,看他被谁引用了,然后看局部变量的relicaFetcer被是谁引用了,看到被kafka的静态变量replicaMannager引用,因此标记为存活对象,且此时系统照样可以创建新的对象,由于这部的进行GC Roots跟踪,比较耗时,

重新标记,由于第二阶段还会产生一部分新的对象,这部分对象就会在重新标记判断是否是存活的对象,然后进行标记,此时有的对象可能已经变成了垃圾对象,有的是存活对像,同时对外停止工作,不在产生新的对象,因为只有少量对象进行标记,因此速度很快

并发清除,就是最后进行清除工作,此时把前几步标记的垃圾对象进行清除,同时系统继续工作,因此这阶段很耗时

其实看完整个CMS垃圾回收机制之后,就会发现他已经尽可能的进行优化了,因为最耗时的即使对全老年代进行GC Roots追踪,标记那些需要回收,那些需要存活,但是第二阶段和第四阶段和系统并发执行,所以这两个阶段最耗时但是对系统没有影响,第一阶段和第三阶段需要STW,但是只是简单的标记而已速度很快,对系统影响也不大。

CMS垃圾回收机制的细节问题

首先我们知道,CMS垃圾回收机制在并发标记和并发清除同时进行垃圾回收和系统工作,这样就会导致一个问题,就是CPU竞争问题,消耗CPU资源

其次,我们的CMS在并发清除阶段,系统还会产生新的对象,因此此时还会产生一些浮动垃圾,虽然他们产生了垃圾,但是此时的并不会回收他们,而是等下一次,所以我可以得出,CMS垃圾回收阶段,还有预留一部分空间,让其他对象进入老年代,

具体什么时候触发老年代的CMS垃圾回收,是可以进行配置的,如下参数,JDK1.6默认的值是92%

“-XX:CMSInitiatingOccupancyFaction”

也就是说老年代占用92%,就会自动进行垃圾回收,预留8%的空间给并发回收期间,系统把新的对象放入老年代,

最后,我要注意的是,如果系统程序放入老年代的对象内存大于可用内存空间怎么办,这个时候就会发生Concurrent Mode Failure,并发垃圾回收失败,此时就会自动使用serial old垃圾回收器替代CMS,就是直接强行把系统程序STW,重新进行GC Roots追踪,标记全部的垃圾对象,然后进行一次性清理,之后恢复系统线程,当然我们要合理的设置CMS垃圾回收比例,尽量避免Concurrent mode Failure

CMS内存碎片的问题

CMS是采用标记-清除算法进行垃圾回收的,因此会产生内存碎片,由于内存碎片会导致频繁的Full GC,

因此有一个参数设置,

-XX:+UseCMSCompactAtFullCollection

他默认是打来,就是在Full GC之后,对内存的碎片进行整理,把存活的对象挪到一起,空出连续的内存空间,避免内存碎片

当然还有一个参数

“-XX:CMSFullGCsBeforeCompaction

他们的默认是0,就是执行了多少次Full GC之后进行内存碎片的整理,默认是每次都要一次内存整理。

为什么CMS垃圾回收慢

新生代垃圾收集块的原因是存活对象少,且直接从GC Roots触发就追踪那些对象是活的就行了,这个速度是非常快的

但是老年代的CMS,存活的对象非常多,且要在零散的内存中找到垃圾对象,速度慢,并且并发清除之后,还要进行内存碎片的整理,最后万一预留的内存不够用还会发生Concurrent Mode Failure问题,使用serial old,导致STW,然后慢慢的重新回收一遍,因此老年代的CMS垃圾回收机制特别慢,

总结一下什么时候发生Full GC

  • 没有打来空间担保参数(-XX:HandlePromotionFailure),每次Minor GC前检查一次,此时如果老年代内存小于新生代对象总大小,发生Full GC,切记在JDK1.6以后就会不在使用
  • 每次Minir GC之前,检查老年代可用内存如果小于历史Minor GC升入老年代的平均对象大小,就会发生Full GC
  • 某次Minor GC存活的对象大于老年代可用空间,就会发生Full GC
  • 设置-XX:CMSInitiatingOccupancyFaction,比如92%,则老年代的内存使用超过92%,就会发生Full GC

最新的G1垃圾回收器的工作原理

我们前面知道了一般系统使用的垃圾收集器都是parNew+CMS进行回收垃圾的,但是他们组合有一个最大的痛点就是Stop the World,因此G1垃圾收集应运而生,

G1垃圾收集器可以同时收集老年代和年轻代,不需要两个垃圾收集器同时运行,他一个人完成多有的工作,

我们可以看到,G1包含了是有逻辑上的老年代和新生代,也就说region此刻是老年代,过一会可能就是新生代了,G1最大的特点就是设置一个垃圾回收的预期停顿时间,G1可以指定垃圾回收的时候,可以保证在一个小时有G1垃圾回收导致的Stop The Word时间,也就是停顿时间,不能超过1分钟

不管是我们如何优化参数,最终的目的就是减少GC,减少GC其实就是减少系统不可用的停顿时间,因此G1可以指定,一个时间内,垃圾回收导致的系统停顿时间不能超过多长时间,全有G1负责

G1是如何做到控制停顿时间的呢

如上图,在堆内存中,我们看到每个Regin有多少垃圾,每个垃圾回收需要多长时间,这个就是G1需要搞清楚的,然后G1在回收的时候,肯定是回收上面20ms就会能回收20M的垃圾

G1垃圾回收器的核心就是,通过被内存分成多个region,以及追踪region的垃圾和回收时长,尽量把垃圾回收对系统的影响控制在你指定的时间范围内,同时回收尽可能多的垃圾对象

要说明的就是region开始不属于任何人,然后接着分配给了新生代,然后垃圾回收之后,同一个region可能就会是老年代,存放长期存活的对象,实际上新生代和老年代各自的内存你区域是不停的变动的,有G1自动控制。

G1实现原理细节问题

G1会把内存最多分为2048个,每个region大小必须是2的倍数,比如堆内存是4G,那个每个region是2M,一共2048个,也可以用下面参数设置Region大小

-XX:G1HeapRegionSize

默认情况下新生代堆内存占比为5%,占据200M内存代销,大概就是100个region,可以使用- XX:G1NewSizePercent设置占比,随着系统的不断运行新生代会不断的分配对象,但是新生代最大不能超过60%,通过“- XX:G1MaxNewSizePercent,但是一旦进行垃圾回收,新生代也会减少

Region也是有新生代和老年代,而新生代也是分为Eden,Survivor,照样可以通过-XX:SurvivorRatio=8设置新生代的占比,比如有新生代100个Region,其中Eden就有80个,而survivor有20个region

新生代的垃圾回收和以前的回收基本一致,如下图

如果我的新生代随着系统的运行一致增长到60%时候,就会进行垃圾回收,此时新生代1200个region,其中1000个region已经被占满了,此时就会进行垃圾回收,

垃圾回收期间会使用复制算法回收,但是会产生Stop The World,把存活的对象放到S1,基本和以前一直,但是还是有区别的,G1可以设置目标GC停顿时间的,也就是G1执行GC最多可以让系统停顿多长时间,可以通过下面参数设置

-XX:MaxGCPauseMills 默认是200ms

最终G1,会跟踪region回收他需要多长时间,可以回收多少对象选择回收一部分的Region,保证G1停顿时间在控制执行范围内,尽可能多的回收一些对象

对象什么时候进入老年代

基本和以前说法一致

  • 在新生代躲过了很多次垃圾回收,达到一定年龄,-XX:MaxTenuringThreshold,
  • 新生代的存活对象超过Survivor的50%

但是我记得,大对象也会进入老年代,但是G1并不这样,他会有专门给大独享的region,同时我们知道新生代占60%,而老年代是40%,那么大对象放在那里呢,

首先我是如何判断大对象呢,其实就是一个对象超过一个region的50%就是大对象,但是有的对象可能很大,此时就会横跨多个region,

如何放大对象,比如新生代占据1200个region,但是每一次垃圾回收就让其中1000个region都空了,此时那么这个1000个region就可以不属于新生代,可以使用这个存放大对象,同时我们要只要到,新生代和老年代垃圾回收的收,同时回收大对象

本文分享自微信公众号 - 洁癖是一只狗(rookie-dog),作者:洁癖汪

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM 《三 JVM 中的垃圾回收器 — — 其他》

    除去上一篇说的CMS,剩下的都是这几类了,然后根据Old or New 自行分配就好了

    邹志全
  • JDK、JRE和JVM三者之间关系

    很多程序员已经写了很长一段时间java了,依然不明白JDK,JRE,JVM的区别。今天个人总结一下它们三者的关系、区别。

    用户7886150
  • JDK、JRE和JVM三者之间的关系

    JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。

    用户7886150
  • JVM三大性能调优参数的含义

    -Xms:设置初始分配大小,默认为物理内存的“1/64” -Xmx:最大分配内存,默认为物理内存的“1/4” -Xss规定了每个线程堆栈的大小。一般情况下2...

    葆宁
  • 1-1.JDK、JRE和JVM三者之间关系

    是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。

    用户7886150
  • JDK、JRE、JVM三者间的联系与区别

    CaesarChang张旭
  • 面试官:JDK、JRE、JVM 三者什么关系?

    这个题目在面试题中基本没怎么出现,但在笔试题中倒是经常见到。所以还是有必要拉出来溜溜~

    田维常
  • JVM学习三

    通常一个java文件,编译之后会变成字节码文件。根据jvm规范,类文件的结构如下:

    路行的亚洲
  • JVM系列三:JVM参数设置、分析

    不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM、GC的参数,可以极大的减少由于GC工作,而导...

    lyb-geek
  • 深入理解JVM(三)——垃圾收集策略详解

    Java虚拟机的内存模型分为五个部分,分别是:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。 这五个区域既然是存储空间,那么为了避免Java虚拟机在运行...

    大闲人柴毛毛
  • JVM(三)对象的生死判定和算法详解

    导读:对象除了生死之外,还有其他状态吗?对象真正的死亡,难道只经历一次简单的判定?如何在垂死的边缘“拯救”一个将死对象?判断对象的生死存活都有那些算法?本文带你...

    Java中文社群-磊哥
  • 每日五分钟,玩转JVM(三):线程独占区

    如果我们对计算机组成有所了解,那么我们一定会知道在计算机中有一块儿特殊的区域,称之为寄存器,寄存器包括了指令寄存器和程序计数器,这两样位于CPU中,作为程序运行...

    山禾说
  • 三、JVM之对象的创建

    上篇博文中已经介绍过了jvm内存的概况,接下来我们从jvm的角度来重新来认识一下Java对象是如何创建。 Java是一门面向对象的语言,在Java程序运行的...

    栋先生
  • JVM系列三(垃圾收集器).

    上篇文章 我们介绍了 Java 内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭,在这几个区域内就不需要过多考虑回收...

    JMCui
  • 用Java实现JVM第三章《解析class文件》

    本案例主要介绍通过java代码从class文件中解析;class文件、常量池、属性表;

    小傅哥
  • 用Java实现JVM第三章《解析class文件》

    案例介绍 本案例主要介绍通过java代码从class文件中解析;class文件、常量池、属性表;

    小傅哥
  • JVM 学习笔记(三)

    https://visualvm.github.io/pluginscenters.html --->选择对应版本链接--->Tools--->Visual G...

    会说话的丶猫
  • 用Java实现JVM第三章《解析class文件》附[classReader拆解]

    案例介绍 按照如下虚拟机规范,本文主要介绍java版本jvm提取class字节码方式。在java中没有无符号类型,例如js中byte取值是0~255、java中...

    小傅哥
  • 用Java实现JVM第三章《解析class文件》附[classReader拆解]

    按照如下虚拟机规范,本文主要介绍java版本jvm提取class字节码方式。在java中没有无符号类型,例如js中byte取值是0~256、java中是-128...

    小傅哥

扫码关注云+社区

领取腾讯云代金券