垃圾回收机制与垃圾回收算法-0723
回顾
1.创建对象的过程,重点在于分配内存,两者方式,以及解决并发安全
2.cas加本地内存缓存解决并发安全
3.对象有对象头,设置对象指针,数组必须有记录长度的数据
4.对象头有哈希码、gc、锁标识、线程持有的锁、偏向线程id,偏向时间戳
5.对象的访问,句柄来存,就是中间多了一个指针,hotspot是直接指针,堆里面的指针直接指向数据
6.方法的执行就是栈帧的执行,数据不可能拿到栈中,太大了,所以都是用引用
7.垃圾回收之前必须要判断对象是否在存活,引用计数法无法解决循环引用的问题。
8.gc roots常见的根包括常量、线程栈变量、常量池、JNI(指针),可达叫不可回收,不可达叫可回收
9.finalize(拯救),方法优先级很低,方法不常用,忘记掉!
10.class的回收时非常严格的,需要四到五个条件,
11.各种引用,reference,强引用,不会被回收
12.对象的分配原则,
1 垃圾回收基础知识
主要是回收堆
- minor gc/young gc
- major gc(不同地方不一样,需要根据出处的上下文理解)/old gc(更好的说法,cms)
- full gc(不仅是新生代、老年代、还要加上方法区(1.7之前是永久代,后面是元空间))
2 分代回收理论
- 大部分对象都是朝生夕死 — 新生代(98%)
- 熬过多次垃圾回收的就越难回收 — 老年代
3 垃圾回收算法
- 复制算法 – 新生
- 标记清除算法、标记整理算法 – 老年
3.1 复制算法
步骤一:先把一般的空间预留,当一般的空间塞满以后,这时就会触发垃圾回收,扫描存活的对象,将存活的copy过去,直接将扫描完的一般格式化(效率高);
步骤二:重复步骤一
步骤一完成后,两个区的身份交换,预留的变成对象分配区域,对象分配区域变成预留
- 特点
- 实现简单、运行高效
- 没有内存碎片
- 利用率只有一半(缺点)
- eden区提高效率,标准的eden:from:to = 8:1:1
- eden区的来源
- appel式回收
- 1.eden区满了,将存活的对象复制到from区
- 2.eden区满了,将存活的对象复制到to区,同时将from区依然存活的复制到to区
- 3.效率由50提高到90,
- 4.如果单次回收超过10%会进入老年代
- 提高空间利用率和空间分配担保
- appel式回收
- 总结
- 用在新生代
3.2 标记清除算法(mark-sweep)
- 根据可达性分析做标记
- 将可回收的进行标记,
- 特点
- 位置不连续,产生碎片
- 对象的分配是一个直接指针,不能分成两段,所以需要对象是连续的,这就要求时连续内存空间
- 效率略低(两次)
- 两遍扫描(还要涉及对象的删除)
- 利用率100%
- 位置不连续,产生碎片
3.3 标记整理算法
- 跟标记清除相似,需要整理内存空间
- 特点
- 没有内存碎片
- 效率偏低
- 两遍扫描、引用指针需要调整(对象保存的地址发生了变换)
4 jvm常用的垃圾回收器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fcuy3mQO-1598880659203)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20200830164616458.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ddJguNNo-1598880659205)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20200830164850376.png)]
4.1 单线程垃圾回收器
- Serial
- SerialOld
需要暂停所有的用户线程,起一个垃圾回收线程
参数
- useSerialGC(鸡肋),只能回收几十兆、几百兆的东西
4.2 多线程垃圾回收器
- Parallel Scavange(默认)
- Paralledl Old (默认)
- ParNew(单独针对cms的)
参数
-
UseParallelGC
-
-XX:MaxGCPauseMillis (没什么鸟用)实际的暂停时间可能还变长了
- 500ms 每隔100s
- 100ms 每隔10s
-
UseAdaptiveSizePolicy
- 自适应生成大小,为了提高吞吐量
-
适用于几百兆到几个g都适用
4.3 stop the world(停顿时间)
如果超过10s,用户体验极其不好,应该不仅仅是为了提高吞吐量
4.4 并发垃圾回收器-cmd
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3tWtCqm4-1598880659206)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20200830171355962.png)]
- 步骤
- 1.初始标记
- 可达性分析(直接的gc roots),同时暂停所有用户线程,暂停时间短,所以可以暂停
- 2.并发标记(让中间标记最长的一段时间并发标记,可以减少stop the wordl的时间)
- 对gc roots下面的元素进行标记,标记的深度很深,数量级很大,所以时间比较长,因此是并发的和用户线程跑
- 这必然会产生
- 3.重新标记
- 重新标记用户线程新产生的垃圾,此时也需要暂停所有用户线程,但是新产生的垃圾少,所以也能很快标记完
- 4.并发清除
- 用户和gc并发执行,时间也比较长
- 最后重置线程
- 1.初始标记
- cms中的问题
- cpu敏感,4核以下,用户很有很强的卡顿感
- 浮动垃圾,最后一步并发清除的过程中又会产生浮动垃圾
- 之前是存到68就触发垃圾回收
- jdk提高到老年代的空间利用率超过92就会触发cms
- 触发垃圾回收相比parallel要提前
- 如果超过了能处理的浮动垃圾
- 此时会用serialOld来取代
- 内存碎片
- 大对象分配非常麻烦
- UseCMSCompactAtFullCollection
- 问题非常多,浮动垃圾和内存碎片导致垃圾回收器随时可能被一个单线程的serialold代替,所以以前老版本经常重启,每天晚上进行重启,来清除浮动垃圾和内存碎片,所以1.6、1.7、1.8都没有将cms设置为默认垃圾回收期
- 为什么是标记清除不是标记整理?
- 因为清除是并发清除,和用户线程并发进行的,可以确保最好并发清除的效率,减少stop the world的时间。
4.5 并发垃圾回收器-g1(garbage first)
设计思想
-
总是跳不出stop the world的问题,所以想着去预测stw、从而实现控制。
-
将整个堆空间划成一个整体,切割成一个个的region,从1m~32m,规定是2的次幂大小,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJ6kpWGt-1598880659208)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20200830172444689.png)]
-
g1逻辑上也是分代的,
-
针对大对象,多了一个Humongous,如果对象大于等于512k,判定为大对象,会放到H区,如果更大,就会放到连续的多个H区
-
eden、survivor用复制,old用标记清除/标记整理、humyong等同于老年代
garbage first
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GmSqB5Bu-1598880659209)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20200830173352644.png)]
-
追求停顿时间(想办法达到?选出其中最有价值的去达到)
-
region区
-
筛选回收
- MaxGcPauseMillis 垃圾回收最大暂定时间,是一个软目标,会尽最大努力去实现,不同于parallel强制去做处理
- 因为划分了区域,不会对整个空间进行垃圾回收,会去选垃圾回收效率比较高的区域进行垃圾回收,这也就是名字的来源。
-
可预测停顿
-
算法—复制和标记整理
步骤
- 初始标记,gc roots,与cms相同,stop the world
- 并发标记,
- 解决回收过程中新分配的对象(不是垃圾),TAMS(top at mark start),每一个region,放一个指针(新new出来的存活的对象)
- 并发标记也会出现漏标问题,SATB(snapshot at the beginning),保存一个快照(内存引用关系)
- 最终标记,处理漏标对象,stop the world
- 筛选回收,不会回收整个堆,通过标记已经算出来,每块区域有多少垃圾,哪个回报率最高,stop the world时间最短,可以获得最大的收益,同时会用到标记整理,
- 相比于cms,是筛选的,
问题
- 解决了cms里面的痛点,大对象,内存碎片
- 但是如果region里的对象与另一个region里的对象有依赖怎么办?
5 cms与g1应该怎么选
5.1 选择
- serial serialold 几十兆到几百兆
- parallel parallel old 几百兆到几个g
- cms 几个g到几十个g (对g1起到铺垫作用,jdk1.8与g1有竞争之力,到jdk1.9可能就不如g1了)
- g1 十几个g到一百多个g(jdk官方文档当堆的空间大于6gb以上或更大,推荐采用g1)
- cms和g1有重合之处,此时可以参考jdk文档
5.2 并不是cms一定比g1差?
每块region都需要一块区域去解决并发标记所需的内存需要,所以花费的内存空间不一定低(漏标、new出来的对象),但是还是应该选择g1,因为不会有那么多烦恼
回顾
1.只有cms是标记清除,其余基本是标记整理
2.避免full gc,相当耗时间
3.g1因为对区域进行了划分,所以可以对清除空间进行选择
4.G1HeapRegionSize,g1区会自动设置
5.cms达不到追求停顿时间的目的,cms只能说是尽可能减少。
6.crud 代码去实现, jvm最重要的关注点是思想
7.full gc也会去回收方法区的内容,回收效率非常低,所以后面换成元空间,不再当成对象。
8.三色标记?