JVM-垃圾回收

目录

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

1.1 引用计数法

1.2 可达性分析算法

1.3 四种引用

2. 垃圾回收算法

2.1 标记清除

2.2 标记整理

2.3 复制

3. 分代垃圾回收

相关 JVM参数

4. 垃圾回收器

4.1 串行 Serial 串行

4.2 吞吐量优先 Parallel 并行的

4.3 响应时间优先 CMS 并发的concurrent

4.4 G1

1)G1 垃圾回收阶段 Garbage First

3)Young Collection +CM (concurrent mark)

4)MixedCollection

5)FullGC

6)Young Collection跨代引用

7)Remark

8)JDK8u20字符串去重

9)JDK8u40并发标记类卸载

10)JDK8u60回收巨型对象

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

12)JDK9更高效的回收

5. 垃圾回收调优

5.1 调优领域

5.2 确定目标

5.3 最快的 GC

5.4 新生代调优

5.5 老年代调优


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

1.1 引用计数法

一个对象被其他变量引用 就让它的计数加1 少一个引用的时候就减1 当没有引用的时候 就是0

但是存在严重的问题 循环引用问题

此时这两个对象是不能被垃圾回收的 会引起内存泄露

1.2 可达性分析算法

Java 虚拟机中的垃圾回收器采用 可达性分析 来探索所有存活的对象

可达性分析 扫描堆中的对象 看是否能够沿着GC Root为起点的引用链找到该对象 找不到则表示可以回收

哪些对象可以作为 GC Root?

  1. java虚拟机栈中的引用的对象;
  2. 方法区中的类静态属性引用的对象;
  3. 方法区中的常量引用的对象;
  4. 本地方法栈中的native方法引用的对象。

1.3 四种引用

1. 强引用

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

2. 软引用(SoftReference)

仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用 对象 可以配合引用队列来释放软引用自身

3. 弱引用(WeakReference)

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

4. 虚引用(PhantomReference)

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

5. 终结器引用(FinalReference) 效率比较低

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

2. 垃圾回收算法

2.1 标记清除

定义:Mark Sweep

优点:速度较快

缺点:会造成内存碎片

2.2 标记整理

定义:Mark Compact

优点:没有内存碎片

缺点:会改变引用的地址 速度慢

2.3 复制

定义:Copy

优点:不会有内存碎片

缺点:需要占用双倍内存空间

3. 分代垃圾回收

新生代:老年代 = 1:2

伊甸园:幸存区From :幸存区To = 8:1:1

长期使用的对象放在老年代中 那种用完了就可以丢弃的放在新生代中

对象首先分配在伊甸园区

新生代空间不足时,触发 minor gc,伊甸园 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from与to

minor gc会引发 stop the world,暂停其它用户的线程等垃圾回收结束,用户线程才恢复运行,当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(对象头 4bit         1111=15)

当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,S T W的时间更长

相关 JVM参数

含义

参数

堆初始大小

-Xms

堆最大大小

-Xmx 或 -XX:MaxHeapSize=size

新生代大小

-Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )

幸存区比例(动态)

-XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy动态调整

幸存区比例

-XX:SurvivorRatio=ratio 默认8 伊甸园8:幸存者0区1:幸存者1区1

晋升阈值

-XX:MaxTenuringThreshold=threshold

晋升详情

-XX:+PrintTenuringDistribution

GC详情

-XX:+PrintGCDetails -verbose:gc

FullGC 前先 MinorGC

-XX:+ScavengeBeforeFullGC

大对象直接晋升到老年代

4. 垃圾回收器

1. 串行

单线程

使用场景:堆内存较小,适合个人电脑

2. 吞吐量优先 少餐多食 吃的多

多线程

使用场景:堆内存较大,多核 cpu

让单位时间内,STW(暂停时间) 的时间最短 0.2+0.2=0.4 ,垃圾回收时间占比最低,这样就称吞吐量高

3. 响应时间优先 少食多餐 吃的快

多线程

使用场景:堆内存较大,多核 cpu

尽可能让单次 STW (暂停时间)的时间最短 0.1+0.1+0.1+0.1+0.1=0.5

4.1 串行 Serial 串行

-XX:+UseSerialGC =Serial + SerialOld

新生代(复制算法) + 老年代(标记+整理算法)

4.2 吞吐量优先 Parallel 并行的

jdk1.8默认使用ParallelGC ParallelOldGC

并行的 标记+整理

-XX:+UseParallelGC~-XX:+UseParallelOldGC

-XX:+UseAdaptiveSizePolicy 动态调整 伊甸园 幸存者区的大小

-XX:GCTimeRatio=ratio 1/1+ratio 默认99 =0.01 调整堆 变大

-XX:GCTimePauserMillis = ms 默认200ms 和上一条有冲突

-XX:ParallelGCThreads=n

和cpu核数相关

4.3 响应时间优先 CMS 并发的concurrent

-XX:+UseConcMarkSweepGC~ -XX:UseParNewGC 可能会退化成串行的~SerialOld

-XX:ParallelGCThreads=n 默认4 ~ -XX:ParallelGCThreads=threads设置成1 剩下3个给用户线程

-XX:CMSInitiatingOccupancyFration=percent

-XX:+CMSScavengeBeforeRemark

标记+清除算法 下面3个红色的是CMS的缺点

初始标记时间很短 并发标记阶段与用户线程并发执行 然后重新标记 最后并发清理

并发清理时清理不掉 浮动垃圾 只能下次清理时才可以清理

第三个参数作用是当到达设定值时 开启GC 为浮动垃圾预留空间 percent默认60

第四个参数作用是在重新标记前 先对新生代进行垃圾回收 可以减轻重新标记的压力

采用的是标记+清除算法会产生内存碎片

4.4 G1

Garbage First         JDK 9 默认G1 取代了CMS

适用场景:

  • 同时注重吞吐量(Throughput)低延迟(Low latency),默认的暂停目标是 200 ms
  • 适合超大堆内存,会将堆划分为多个大小相等的region(区) 1M 2M 4M 8M 。。
  •  
    • 整体上是标记+整理算法,两个区域之间复制算法

相关 JVM 参数

jdk9及以上 默认使用G1垃圾回收器 之前版本是CMS 想开启的话 需要下面第一行指令 jdk9后不用

-XX:+UserG1GC

-XX:G1HeapRegionSize=size 设置成1M 2M 4M 8M 。。

-XX:MaxGCPauseMillis=time 默认200 ms

可以避免空间碎片

1)G1 垃圾回收阶段 Garbage First

2)YoungCollection

会触发 STW(stop the world) 时间较短

白色的是空闲的

青色的是伊甸园区

复制进幸存区

幸存区该晋升的晋升到老年代

没有到阈值的再次拷贝到幸存区

3)Young Collection +CM (concurrent mark)

在 Young GC 时会进行 GC Root 的初始标记

老年代占用堆空间比例达到阈值时,进行并发标记 不会STW,由下面的 JVM 参数决定 默认 45%

4)MixedCollection

会对 E、S、O 进行全面垃圾回收

最终标记(Remark)会 STW

拷贝存活(Evacuation)会 STW

G1会有选择性的复制O到O 先进行垃圾回收 这样复制的就变少 垃圾回收的时间也少 快

5)FullGC

SerialGC 串行

        新生代内存不足发生的垃圾收集 - minor gc

        老年代内存不足发生的垃圾收集 - full gc

ParallelGC 并行

        新生代内存不足发生的垃圾收集 - minor gc

        老年代内存不足发生的垃圾收集 - full gc

CMS

        新生代内存不足发生的垃圾收集 - minor gc

        老年代内存不足 分两种情况

                当回收速度快于产生速度时不叫full gc

                反之为full gc

G1(jdk1.9之后 此算法为默认的)

        新生代内存不足发生的垃圾收集 - minor gc

        老年代内存不足 分两种情况

                当回收速度快于产生速度时不叫full gc

                反之为full gc

6)Young Collection跨代引用

新生代回收的跨代引用(老年代引用新生代)问题

老年代再次细分 每个新的小表为512KB

卡表与Remembered Set 写屏障 异步操作

在引用变更时通过 post-write barrier + dirty card queue 脏卡队列

concurrent refinement threads 更新Remembered Set

7)Remark

pre-write barrier + satb_mark_queue

黑色的是已经处理完的 存活

灰色的是正在处理中的

白色是未处理的

处理后 中间那个白色 因为没有被强引用 最终会被回收

重新标记 就是对标记进行进一步的检查

当对象的引用发生改变时 jvm会加入一个写屏障 然后把C放入一个队列 并变成灰色

再依次进行检查 发现有强引用在引用C 就会变成黑色 就不会被误当成垃圾回收掉

8)JDK8u20字符串去重

优点:节省大量内存

缺点:略微多占用了 cpu 时间,新生代回收时间略微增加

将所有新分配的字符串放入一个队列

当新生代回收时,G1并发检查是否有字符串重复

如果它们值一样,让它们引用同一个 char[]

注意,与String.intern()不一样

String.intern()关注的是字符串对象

而字符串去重关注的是 char[]

在 JVM 内部,使用了不同的字符串表

9)JDK8u40并发标记类卸载

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

10)JDK8u60回收巨型对象

一个对象大于 region 的一半时,称之为巨型对象

G1 不会对巨型对象进行拷贝 回收时被优先考虑

G1会跟踪老年代所有 incoming引用,老年代 incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉

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

并发标记必须在堆空间占满前完成,否则退化为 FullGC

JDK9之前需要使用 -XX:InitiatingHeapOccupancyPercent

JDK9可以动态调整

-XX:XX:InitiatingHeapOccupancyPercent 用来设置初始值

进行数据采样并动态调整 总会添加一个安全的空档空间

12)JDK9更高效的回收

250+增强

180+bug修复

Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide, Release 12

5. 垃圾回收调优

明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则

-XX:+PrintFlagsFinal -version / findstr "GC"

5.1 调优领域

内存

锁竞争

cpu 占用

io

5.2 确定目标

【低延迟】还是【高吞吐量】,选择合适的回收器

CMS,G1,ZGC 低延迟 jdk9中默认使用G1

ParallelGC Zing 高吞吐量

Zing

5.3 最快的 GC

答案是不发生GC

查看 FullGC 前后的内存占用,考虑下面几个问题:

        数据是不是太多?

                resultSet = statement.executeQuery("select * from 大表 limit n")

        数据表示是否太臃肿?

                对象图

                对象大小 16 Integer 24 int 4

        是否存在内存泄漏?

                static Map map =

                软

                弱

                第三方缓存实现

5.4 新生代调优

新生代的特点:

        所有的 new 操作的内存分配非常廉价

                TLAB thread-local allocation buffer

        死亡对象的回收代价是零

        大部分对象用过即死

        Minor GC 的时间远远低于 Full GC


        新生代能容纳所有【并发量 * (请求-响应)】的数据

        幸存区大到能保留【当前活跃对象+需要晋升对象】

        晋升阈值配置得当,让长时间存活对象尽快晋升

5.5 老年代调优

以 CMS 为例

        CMS 的老年代内存越大越好

        先尝试不做调优,如果没有 Full GC 那么已经...,否则先尝试调优新生代

        观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

                -XX:CMSInitiatingOccupancyFraction=percent

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋文轩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值