GC面试复习题
2021-02-19 17:53:49 0 举报
AI智能生成
java面试
作者其他创作
大纲/内容
垃圾回收
基本步骤
查找内存中不再使用的对象
引用计数法
如果一个对象没有被任何引用指向,则可视之为垃圾
缺点: 不能检测到环的存在。
根搜索算法(可达性分析)
通过一系列名为”GC Roots”的对象作为起始点
从这些节点开始向下搜索
搜索所走过的路径称为引用链(Reference Chain)
当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的
释放这些对象占用的内存
复制或者直接清理
直接清理会存在内存碎片
清理再压缩的方式
标记-清理
标记
标记出需要回收的对象
清除
标记完成之后统一清除对象
优点:是效率高
缺点:是容易产生内存碎片
分支主题
标记-复制
将可用内存容量划分为大小相等的两块,每次只使用其中的一块
一块用完之后,将还存活的对象复制到另外一块上,然后把已使用的内存空间一次理掉
内存使用率不高,只有原来的一半
让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存
分支主题
标记-整理
标记操作与“标记-清理”算法一致
后续操作不是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动
更新引用其对象的指针
优点:解决了标记-清理算法存在的内存碎片问题。
缺点:仍需要进行局部对象移动,一定程度上降低了效率。
分支主题
分代的假设
由于对象的存活时间有长有短,所以对于存活时间长的对象,减少被gc的次数可以避免不必要的开销
新生代
存放刚创建的和存活时间比较短的对象
老年代
放存活时间比较长的对象
分支主题
垃圾收集器的历史
Serial(串行)收集器
单线程的收集器
进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束
-XX:+UseSerialGC
Parallel(并行)收集器
吞吐量收集器
使用多线程去完成垃圾清理工作
充分利用多核的特性,大幅降低gc时间
-XX:+UseParallelGC -XX:+UseParallelOldGC
CMS(并发)收集器
收集器在Minor GC时会暂停所有的应用线程
以多线程的方式进行垃圾回收
Full GC时不再暂停应用线程
使用若干个后台线程定期的对老年代空间进行扫描
及时回收其中不再使用的对象
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
新生代默认空间占比总空间 1/3,老生代默认占比 2/3
新生代:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用
在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。
G1(并发)收集器
垃圾优先收集器
设计初衷
尽量缩短处理超大堆(大于4GB)时产生的停顿
相对于CMS的优势是:内存碎片的产生率大大降低
-XX:+UseG1GC
CMS的优势
可预测的停顿模型
避免了CMS的垃圾碎片
超大堆的表现更出色
分支主题
G1
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
-XX:+UseG1GC为开启G1垃圾收集器
-Xmx32g 设计堆内存的最大内存为32G
XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms
在内存大小一定的情况下,我们只需要修改最大暂停时间即可
G1将新生代,老年代的物理空间划分取消了
分支主题
GC
概念
将堆划分为若干个区域(Region)
仍然属于分代收集器
分支主题
G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作
在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了
Humongous区域
一个对象占用的空间超过了分区容量50%以上
存放巨型对象
一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储
可能引发GC
在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold
如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 full GC
H-obj直接分配到了old gen,防止了反复拷贝移动
H-obj在global concurrent marking阶段的cleanup 和 full GC阶段回收
为了减少连续H-objs分配对GC的影响,需要把大对象变为普通的对象,建议增大Region size
对象分配策略
TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
为了使对象尽可能快的分配出来
Eden区中分配
Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB
线程之间不再需要进行任何的同步
Humongous区分配
回收的时候可以专注于收集垃圾多的分区
-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数
如果不设定,那么G1会根据Heap大小自动决定
SATB(Snapshot-At-The-Beginning)
G1并发的基础就是SATB
GC开始时活着的对象的一个快照
通过Root Tracing得到的,作用是维持并发GC的正确性
RSet(Remembered Set)
跟踪指向某个heap区内的对象引用
point-in(谁引用了我的对象):哪些分区引用了当前分区中的对象
仅仅将这些对象当做根来扫描就避免了无效的扫描
分支主题
每一个内存分段都对应一个RS
RS保存了来自其他分段内的对象对于此分段的引用
年轻代的内存分段(Eden和Survivor区的内存分段)RS只保存来自老年代的对象的引用
老年代分段的RS来说,也只会保存来自老年代的引用
因为老年代的回收之前会先进行年轻代的回收,年轻代回收后Eden区变空了,G1会在老年代回收过程中扫描Survivor区到老年代的引用
Post-write barriers
屏障代码在写操作之后(因此名称是“post-write barrier”),为了记录帮助追踪跨region更新
包含更新引用字段的card更新可靠的日志缓冲区
一旦缓冲区满了,它们就停止工作
Concurrent refinement threads
通过并发更新它们来帮助维护RSets(通常在应用运行期间)
处理这些缓冲区日志
-XX:G1ConcRefinementThreads或者-XX:ParallelGCThreads控制
粒度
一个Per-Region-Table (PRT)是RSet存储颗粒度级别一个抽象
Sparse
一个包含Card目录的hash table
card包含来自region的引用
region的引用是card到“owning region”的关联的地址
Fine
开放的hash table
每一个entry代表一个指向owning region的引用的region
region里面的card目录,是一个bitmap
当达到fine-grain PRT的最大容量,coarse grain bitmap里面的相应的coarse-grained bit被设置,相应地entry从 fine grain PRT删除
Coarse
coarse bitmap有一个每个region对应的bit
coarse grain map设置bit意味着关联的region包含到“owning region”的引用
辅助GC
YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation
mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region
大大减少了GC的工作量
CSet(Collection Set)
记录GC要收集的Region的集合
CSet里的Region可以是任意代的
GC时,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可
Card Table
points-out(我引用了谁的对象)
将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡
卡通常较小,介于128到512字节之间
Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址
默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来
一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
Pause Prediction Model(停顿预测模型)
G1根据模型统计,计算出来的历史数据来预测本次收集需要选择的Region数量
尽量满足用户设定的目标停顿时间
收集模式
Young GC和Mixed GC,两种都是Stop The World(STW)
Young GC
主要是对Eden区进行GC,它在Eden空间耗尽时会被触发
den空间的数据移动到Survivor空间中
如果Survivor空间不够,Eden空间的部分数据晋升老年代
最终Eden空间的数据为空,GC停止工作,应用线程继续执行
分支主题
分支主题
Young GC 阶段
阶段1:根扫描
静态和本地对象被扫描
阶段2:更新RS
处理dirty card队列更新RS
阶段3:处理RS
检测从年轻代指向年老代的对象
阶段4:对象拷贝
拷贝存活的对象到survivor/old区域
阶段5:处理引用队列
软引用,弱引用,虚引用处理
步骤
1. 选定young generation region的RSet作为根集
记录了old->young的跨代引用,避免了扫描整个old generation
Mix GC
不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区
Mixed GC不是full GC,它只能回收部分老年代的Region
如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap
步骤
全局并发标记(global concurrent marking)
为Mixed GC提供标记服务的, 并不是一次GC过程的一个必须环节
五个步骤
初始标记(initial mark,STW)
G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关
根区域扫描(root region scan)
初始标记的存活区扫描对老年代的引用,并标记被引用的对象
该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收
并发标记(Concurrent Marking)
在整个堆中查找可访问的(存活的)对象
该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断
最终标记(Remark,STW)
该阶段是 STW 回收,帮助完成标记周期
G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理
清除垃圾(Cleanup,STW)
G1 GC 执行统计和 RSet 净化的 STW 操作
在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域
清理阶段在将空白区域重置并返回到空闲列表时为部分并发
拷贝存活对象(evacuation)
Evacuation阶段是全暂停的
把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理
第一阶段选出来的Region中筛选出任意多个Region作为垃圾收集的目标,这些要收集的Region叫CSet,通过RSet实现
old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region
三色标记算法
描述追踪式回收器的一种有用的方法
利用它可以推演回收器的正确性
对象三种类型
黑色
根对象,或者该对象与它的子对象都被扫描
灰色
对象本身被扫描,但还没扫描完该对象中的子对象
白色
未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象
垃圾对象
float garbage
如果被替换的白对象就是要被收集的垃圾,这次的标记会让它躲过GC
因为SATB的做法精度比较低,所以造成的float garbage也会比较多
步骤
1. 根对象被置为黑色,子对象被置为灰色。
分支主题
2.由灰色遍历,将已扫描了子对象的对象置为黑色
分支主题
3.遍历了所有可达的对象后,所有可达的对象都变成了黑色
不可达的对象即为白色,需要被清理
分支主题
对象丢失问题(白对象漏标)
前提
Mutator赋予一个黑对象该白对象的引用。
Mutator删除了所有从灰对象到该白对象的直接或者间接引用。
当垃圾收集器扫描到下面情况时
分支主题
2.执行
分支主题
分支主题
3.再标记扫描
分支主题
此时C是白色,被认为是垃圾需要清理掉,显然这是不合理的
解决方案
在插入的时候记录对象
在删除的时候记录对象
常用参数
-XX:+UseG1GC
使用 G1 垃圾收集器
-XX:MaxGCPauseMillis=200
设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)
-XX:InitiatingHeapOccupancyPercent=45
启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45.
-XX:NewRatio=n
新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n
eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n
提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n
设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n
并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n
设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n
使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
最佳实践
不断调优暂停时间指标
暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC
不要设置新生代和老年代的大小
G1收集器在运行的时候会调整新生代和老年代的大小
过改变代的大小来调整对象晋升的速度以及晋升年龄
从而达到我们为收集器设置的暂停时间目标
设置了新生代大小相当于放弃了G1为我们做的自动调优
设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小
关注Evacuation Failure
G1日志分析
从整体上看,并发标记周期和混合回收的前后都有可能穿插着新生代GC
并发标记周期主要是回收老年代空间,当然也包含了一次新生代GC
分支主题
Evacuation Failure
错误日志
evacuation failure
to-space exhausted
to-space overflow
promotion failure
产生原因
Java类何时会被加载
1:遇到new、getstatic、putstatic 等指令时。
2:对类进行反射调用的时候。
3:初始化某个类的子类的时候。
4:虚拟机启动时会先加载设置的程序主类。
5:使用JDK 1.7 的动态语言支持的时候。
当运行过程中需要这个类的时候
怎么加载类
分支主题
JVM 默认用于加载用户程序的ClassLoader为AppClassLoader
不过无论是什么ClassLoader,它的根父类都是java.lang.ClassLoader
分支主题
分支主题
分支主题
分支主题
0 条评论
下一页