jvm2-垃圾回收器及优化

一. 如何判断对象可以回收

1.1 引用计数法

在这里插入图片描述

1.2 可达性分析算法

Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象。扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
哪些对象可以作为 GC Root ?

  • 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中应用的对象。
  • 方法区中的类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(native方法)引用的对象
    例子:
    在这里插入图片描述Thread;当前活跃线程里的对象

可以通过MAT-javabasic-GCroot查看

1.3 四种引用

  1. 强引用
    只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

  2. 软引用(SoftReference)
    仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象可以配合引用队列来释放软引用自身
    用处:一些不重要且较大的内存对象,可以用软引用,减少内存溢出
    在这里插入图片描述

  3. 弱引用(WeakReference)–ThreadLocal
    仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象可以配合引用队列来释放弱引用自身

  4. 虚引用(PhantomReference)
    必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

  5. 终结器引用(FinalReference)
    无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象

二. 垃圾回收算法

2.1 标记清除

定义: Mark Sweep
速度较快;产生内存碎片
清除的过程并不是把内存清空,而是将该空间起始和结束位置释放,之后又对象存储能放下就直接使用。
在这里插入图片描述

2.2 标记整理

定义:Mark Compact
速度慢(牵扯内存区块和地址引用的变动);无碎片
在这里插入图片描述

2.3 复制

定义:Copy
不会有内存碎片
需要占用双倍内存空间
在这里插入图片描述
复制–>清空–>互换

原理

MinorGC会把Eden中的所有或的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old Generation中,也即一旦收集后,Eden区就变成空的了。

当对象在Eden(包括一个Survivor区域,这里假设是from区域)出生后,在经过一次MinorGC后,如果对象还存活,并且能够被另外一块Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域又足够的内存空间来存储Eden和from区域中存活的对象),则使用复制算法将这些仍然还存活的对象复制到另外一块Survivor区域(即to区)中,然后清理所有使用过的Eden以及Survivor区域(即from区),并且讲这些对象的年龄设置为1,以后对象在Survivor区没熬过一次MinorGC,就将对象的年龄+1,当对象的年龄达到某个值时(默认15,通过-XX:MaxTenuringThreshold来设定参数),这些对象就会成为老年代。

-XX:MaxTenuringThreshold设置对象在新生代中存活的次数

解释

HotSpot JVM把年轻代分为了三部分:1个Eden区和两个Survivor区,默认比例是8:1:1,一般情况下,新创建的对象都会被分配到Eden区,这些对象经过第一次的MinorGC后,如果仍然存活,将会被移到Survivor区。对象Survivor区中每熬过一次MinorGC,年龄就增加一岁,当他的年龄增加到一定程度时,就会被移动到年老代中。因为年轻代中的对象基本都是朝生夕死(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

复制要交换,谁空谁是to

2.4小结

  • 内存效率:复制算法>标记清除算法>标记整理算法
  • 内存整齐度:复制算法=标记整理算法>标记清除算法
  • 内存利用率:标记整理算法=标记清除算法>复制算法

三. 分代垃圾回收

在这里插入图片描述
分区能对不同对象进行更有效的处理。老年代生命周期长,不需要频繁GC
类别扔垃圾,厨余垃圾每天都扔,不要的大件先堆存,很久才扔一次(耗时久)
1)对象首先分配在伊甸园区域
2)新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to
3) minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
4)当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
(或者内存特别紧张,大对象新生代放不下,就算不够15次,也会移到老年代)
5)当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长

3.1相关jvm参数

堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例 -XX:SurvivorRatio=ratio
晋升阈值 -XX:MaxTenuringThreshold=threshold
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC

四. 垃圾回收器

1. 串行Serial

单线程
堆内存较小,适合个人电脑
复制+标记整理 1.8之前
在这里插入图片描述

2. 吞吐量优先Parallel

多线程
堆内存较大,多核 cpu
让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
1.8默认方式 回收线程数和cpu核数一致,fullGC时cpu 100%
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC

在这里插入图片描述

3. 响应时间优先CMS

多线程
堆内存较大,多核 cpu
尽可能让单次 STW 的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5
CMS回收器在垃圾回收的某些阶段不用stw,对用户线程影响小
并发标记和清理(不同于上面的并行),并发时用户线程正常执行
-XX:+UseConcMarkSweepGC ~ 浮动垃圾多退化为后面的 -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=cpu核心数的1/4
-XX:CMSInitiatingOccupancyFraction=老年代百分百
-XX:+CMSScavengeBeforeRemark

重新标记的作用
为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录

在下图GC过程中又生成的垃圾叫浮动垃圾,浮动垃圾过多可能退化为单线程GC!!!所以老年代大点好
在这里插入图片描述

4 G1

定义:Garbage First
2004 论文发布
2009 JDK 6u14 体验
2012 JDK 7u4 官方支持
2017 JDK 9 默认
适用场景
同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
超大堆内存,会将堆划分为多个大小相等的 Region
整体上是 标记+整理 算法,两个区域之间是 复制 算法
相关 JVM 参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time

1)G1垃圾回收阶段

在这里插入图片描述

2) Young Collection

会 STW

3) Young Collection + CM

在 Young GC 时会进行 GC Root 的初始标记
老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定

4) Mixed Collection

会对 E、S、O 进行全面垃圾回收
最终标记(Remark)会 STW
拷贝存活(Evacuation)会 STW
-XX:MaxGCPauseMillis=ms

5) Full GC

SerialGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
ParallelGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
CMS
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足
G1
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足

6) Young Collection

跨代引用新生代回收的跨代引用(老年代引用新生代)问题
在这里插入图片描述

7) Remark

pre-write barrier + satb_mark_queue
在这里插入图片描述

8) JDK 8u20 字符串去重

优点:节省大量内存
缺点:略微多占用了 cpu 时间,新生代回收时间略微增加
-XX:+UseStringDeduplication
在这里插入图片描述

9) JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类 -XX:+ClassUnloadingWithConcurrentMark 默认启用

10) JDK 8u60 回收巨型对象

一个对象大于 region 的一半时,称之为巨型对象
G1 不会对巨型对象进行拷贝
回收时被优先考虑
G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉

11) JDK 9 并发标记起始时间的调整

并发标记必须在堆空间占满前完成,否则退化为 FullGC
JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
JDK 9 可以动态调整
-XX:InitiatingHeapOccupancyPercent 用来设置初始值
进行数据采样并动态调整
总会添加一个安全的空档空间

5、选择垃圾回收器

在这里插入图片描述
Parallel有三个收集器,New,Scavenge(默认)和Old。前两个类似都是新生代复制算法。

  • 单CPU或小内存,单机内存

    • XX:+UseSerialGC
  • 多CPU,需要最大吞吐量,如后台计算型应用

    • XX:+UseParallelGC -XX:+UseParallelOldGC
  • 多CPU,最求低停顿时间,需快速相应,如互联网应用

    • XX:+ParNewGC -XX:+UseConcMarkSweepGC
参数新生代收集器新生老年代垃圾收集器老年
UseSerialGCSerialGC复制SerialOldGC标整
UseParNewGCParNew复制SerialOldGC标整
UseParallelGC
UseParallelOldGC
Parallel[Scavenge]复制Parallel Old标整
UseConcMarkSweepGCParNew复制CMS+Serial Old的收集器组合(Serial Old 作为CMS出错的后备收集器)标清
UseG1GCG1整体上采用标整局部是通过复制算法

在这里插入图片描述

和CMS相比优势

  1. G1不会产生内存碎片
  2. 可以精确控制停顿。该收集器把堆划分成多个固定大小的区域,每根据允许停顿的时间去收集垃圾最多的区域在这里插入图片描述

五. 垃圾回收调优

参考oracle官方文档,最新且权威。随jdk版本越来越高效

5.1 调优领域

内存
锁竞争
cpu 占用
io

5.2 确定目标

【低延迟】还是【高吞吐量】,选择合适的回收器
低延迟:CMS(主流),G1(趋势),ZGC(jdk12)
高吞吐:ParallelGC
Zing:(非hotspot)几乎没有stw,高吞吐

5.3 最快的 GC

答案是不发生 GC
查看 FullGC 前后的内存占用,考虑下面几个问题:
1)数据是不是太多?resultSet = statement.executeQuery(“select * from 大表 limit n”)
2)数据表示是否太臃肿?对象图
3)是否存在内存泄漏?
static Map map =xx
推荐:软\弱引用 或者第三方缓存实现redis

5.4 新生代调优

新生代的特点:所有的 new 操作的内存分配非常廉价
TLAB thread-local allocation buffer
死亡对象的回收代价是零,大部分对象用过即死
Minor GC 的时间远远低于 Full GC

新生代能容纳所有【并发量 * (请求-响应)】的数据
幸存区大到能保留【当前活跃对象+需要晋升对象】
晋升阈值配置得当,让长时间存活对象尽快晋升
-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistribution
在这里插入图片描述

5.5 老年代调优

以 CMS 为例
CMS 的老年代内存越大越好
先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent

5.6 案例

案例1 Full GC 和 Minor GC频繁
增大新生代
案例2 请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)
查看GC日志,重新标记阶段耗时最长。是新生代也参与标记耗时。加参数-XX:+CMSScavengeBeforeRemark
案例3 老年代充裕情况下,发生 Full GC (CMS jdk1.7)
1.7永久区也fullGC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值