目录
3)Young Collection +CM (concurrent mark)
1. 如何判断对象可以回收
1.1 引用计数法
一个对象被其他变量引用 就让它的计数加1 少一个引用的时候就减1 当没有引用的时候 就是0
但是存在严重的问题 循环引用问题
此时这两个对象是不能被垃圾回收的 会引起内存泄露
1.2 可达性分析算法
Java 虚拟机中的垃圾回收器采用 可达性分析 来探索所有存活的对象
可达性分析 扫描堆中的对象 看是否能够沿着GC Root为起点的引用链找到该对象 找不到则表示可以回收
哪些对象可以作为 GC Root?
- java虚拟机栈中的引用的对象;
- 方法区中的类静态属性引用的对象;
- 方法区中的常量引用的对象;
- 本地方法栈中的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