垃圾回收器

方法区和堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

什么是永久代和元空间?

方法区是一种规范,不同的虚拟机厂商可以基于规范做出不同的实现,永久代和元空间就是出于不同jdk版本的实现。

说白了,方法区就像是一个接口,永久代与元空间分别是两个不同的实现类而已。只不过永久代是这个接口最初的实现类,后来这个接口一直进行变更,直到最后彻底废弃这个实现类,由新实现类——元空间进行替代。

 

方法的局部变量、类的静态变量可以作为GCRoot 而类的实例变量不是GCRoot

 

 

新生代的垃圾回收器 主要有 Serial 、ParNew  回收算法都是使用的 复制算法 新生代也会有STW

Serial 是单线程回收 而ParNew使用的是多线程回收  默认的线程数等于CPU核数 大型后台系统一般是用ParNew回收器 而一些window的客户端程序又可能会使用Serial回收器

 

老年代的垃圾回收器一般是用 CMS  采用的是 “标记-清理算法” 会产生内存碎片 所以CMS有一个参数“-XX:+UseCMSCompactAtFullCollection” 默认开启 意思是在进行fullGC之后 进行碎片整理 把存活对象挪动到一起 腾出连续的内存 这个过程会STW。 “-XX:CMSFullGCsBeforeCompaction” 参数规定几次fullGC后进行碎片整理 默认0 就是每次fullGC都整理。

 

.为了减少STW的时间 CMS  尽量让垃圾回收线程和工作线程一起执行 默认的垃圾回收线程数=(CPU核数 + 3)/4 向下取整

CMS在执行垃圾回收时分成四个阶段 1、初始标记 2、并发标记 3、重新标记 4、并发清理

1、初始标记阶段标记出来所有GCRoot直接引用的对象  会导致STW 让系统的工作线程全部停止 只是标记GCRoot直接饮用的对象 时间很短

2、并发标记  不会导致STW  这个过程有可能会创建新的对象也可能有些对象变成垃圾, 回收线程会尽可能的对已有的对象GCRoot进行追踪, 需要追踪所有的对象是否被GCTRoot直接或间接引用 比较耗时

3、重新标记  这个阶段会导致 STW  因为需要对第二阶段过程中产生或者变动过的对象进行标记 比较快

4、并发清理 不会STW  就是清除此前标记为垃圾的对象 

 

在并发清理期间还是可以正常创建对象的,这期间的对象只能到下一次GC才能被回收了  所以CMS不能在老年代满了之后出发的而是占用达到一定比例后就会触发 默认是92%

 

concurrent mode failure

如果在CMS垃圾回收期间,要放入老年代的对象大小大于预留的8%可用内存了 此时就会自动启用Serial Old垃圾回收器代替CMS 强行STW 重新进行长时间的GC Root追踪 标记出全部垃圾对象  不允许新的对象产生 一次性把垃圾对象都回收掉 之后再恢复系统线程。所以可以适当的对92%这个触发参数进行优化 避免concurrent mode failure的问题

 

G1垃圾回收器

G1垃圾回收器 可以同时回收新生代和老年代对象 不需要两个垃圾回收器拼配合 特点是它把堆内存分为多个大小相等的region, 也有新生代老年代的概念 不过是逻辑概念 新生代包含了一部分region 老年代包含了某些region    主要的特点就是 我们可以通过G1来设置垃圾回收过程中的预期STW的时间   比如 设置一小时里回收期间STW的时间不超过一分钟。(优化jvm的主要目的就是减少gc的次数进而减少stw时系统不可用的时间)

它可以知道每个region有多少垃圾对象、回收大概耗时多久   可以做到让人为来定义垃圾回收对系统的影响, 通过把内存分为多个region 追踪每个region里有多少垃圾对象, 回收需要多少时间 进而在垃圾回收的时候把STW的时间控制在设定的范围内 同时尽可能的回收更多的垃圾对象。

 

每个region 可能属于新生代也可能属于老年代, 刚开始一个region可能谁都不属于, 如果开始分配对象的时候分给了新生代那么在垃圾回收之后下次分配可能就分到了老年代, 所以就没有新生代分配多少内存老年代分配多少内存这一说了。

 

JVM最多能有2048个region 每个region的大小必须是2的倍数,  设置好堆内存的大小 如果发现用的是G1垃圾回收器则会用堆内存的大小除以2048计算每个region的大小 “XX:+UseG1GC”来指定使用G1垃圾回收器

“-XX:G1HeapRegionSize”: 手动设置每个 Region 的大小。值是2的幂,范围是 1MB 到 32MB 之间,目标是根据最小的 Java 堆大小划分出约2048个区域。默认是堆内存的1/2000

“-XX:MaxGCPauseMillis”:设置期望达到的最大 GC 停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms。

JVM给新生代的region最多不会超过60%这个比例是动态的, G1也是有eden区和survivor区的 比如新生代如果有100个region则80个是eden区的10个survivor1区的10个survivor2区的。

 

当新生代region内存大小占据了堆内存的60%, 而且eden区占满了对象就会触发新生代的GC 还是用的复制算法 将eden区的region中存活对象复制到survivor1区中 之后清空eden区的region 会导致 STW 但是不同的是   它可以知道每个region有多少垃圾对象、回收大概耗时多久   可以做到让人为来定义垃圾回收对系统的影响, 通过追踪每个region里有多少垃圾对象, 回收需要多少时间 选择回收一部分的region 进而在垃圾回收的时候把STW的时间控制在设定的范围内 同时尽可能的回收更多的垃圾对象

 

使用G1时对象进入老年代的时机跟ParNew差不多  区别在对大对象的处理上 G1不会把大对象直接放入老年代 而是有专门的region来存放大对象 一个对象大小超过一个region的50%就会判定为大对象 如果一个region是2M那超过1M的都是大对象 特别大的对象还会跨region存放  新生代和老年代的gc都会顺带的把存放大对象的region也gc一下

 

新生代加老年代的混合垃圾回收

“-XX:InitiatingHeapOccupancyPercent”  默认值是45%。 当老年代的region占用堆内存超过45%时  会尝试进行新生代和老年代一起回收的混合回收。第一步是进行“初始标记” 标记一下每个线程的栈内存中局部变量代表的GCRoot和方法区中的静态变量代表的GCRoot会被GCRoot 直接引用的对象, 会导致STW 过程比较短

第二步“并发标记” 这个过程不会STW  会从GCRoot开始追踪到所有存活对象  在此阶段如果有对象被新建了或者失去引用了都会被JVM记录下来  比较耗时但是不会STW

第三步 “最终标记” 根据“并发标记”阶段记录的对象修改 最终确认哪些是垃圾对象哪些是存活对象  会导致STW

第四步 “混合回收”  计算老年代中每个region里存活对象的数量、占比、执行垃圾回收的预期效率花费时间

接着是停止系统程序STW 选择部分region进行回收 尽可能的回收更多的垃圾 而不会超过我们预设的STW时间

 

 

posted @ 2022-08-01 18:38  99-1  阅读(172)  评论(0)    收藏  举报