「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
1.垃圾收集算法
首先,先了解一下分代收集理论
分代收集理论建立在两个假说之上:
1⃣️弱分代假说(对象都是朝生夕灭)
2⃣️强分代假说(熬过越多次垃圾回收的对象就越难以消亡)
由此推出垃圾收集器的设计原则:收集器应该将Java堆划分出不同的区域,将回收对象按照年龄划分到不同的区域。
对象不是孤立的,存在跨代引用。每次进行新生代回收的时候,需要遍历一次老年代所有的对象来确保可达性分析结果的准确,这个操作是很大的负担。
由此,推出了第三条经验法则:
3⃣️跨代引用假说(存在引用关系的对象,倾向于同时生存、同时消亡)
在新生代上建立一个全局数据结构("记忆集"),这个结构把老年代划分为若干块,标识老年代中存在跨代引用的内存。之后进行Minor GC时,只会对标记的内存进行GC Roots扫描。
标记-清除算法
步骤:1⃣️标记 2⃣️清除
标记出需要需要回收的对象,标记完成后,回收所有被标记的对象。或者反过来,标记不需要回收的对象,回收所有未被标记的对象。
缺点:
1⃣️执行效率
如果大量的对象需要回收,这时候就需要进行大量的标记清除。对象越多,效率越低。
2⃣️内存空间碎片化
标记清除后,会产生不连续的内存空间,就会没有足够的内存给较大的对象分配空间。
标记-复制算法
为了解决标记-清除算法的执行效率低问题,此算法是将内存划分成两块容量相等的空间,每次只使用其中一块。当使用的这块内存用完了,就把存活的对象复制到另一块上面,再把使用过的这块内存空间清理。
缺点:
1⃣️当有大量的存活对象时,这样就会有大量的复制操作。适用有大量对象需要回收的场景。
2⃣️内存减半,造成空间浪费。
目前大多数虚拟机都采取标记-复制算法,基于标记-复制算法之上提供了一种更优的回收方式:Appel式回收。
标记-整理算法
针对老年代,大量对象都是存货状态,不能采用标记-复制算法。
步骤:1⃣️标记 2⃣️将所有存活的对象向内存空间的一端移动 3⃣️清理边界外的内存
移动对象,内存回收时复杂。
不移动对象,内存分配时复杂。
考虑到系统的吞吐量,移动对象更为合适。
安全点
导致引用关系发生变化(导致OopMap内容变化)的指令非常多,如果为每一条指令都从创建对应的OopMap,这需要大量的存储空间,垃圾回收的成本就太高了。
为此,引出安全点这个概念。
用户程序必须执行到安全点后才能暂停。
安全点位置的选定规则:"具有让程序长时间执行的特征" ,长时间执行:例如方法调用、循环跳转、异常跳转等指令序列复用。
当发生垃圾收集时,如何让所有的线程都执行到最近的安全点并停顿呢?
1⃣️抢先式终端
2⃣️主动式终端
设置一个标志位,每个线程都不停的去轮询这个标志位,当标志位为'True'时,就在最近的安全点上主动中断挂起。
安全区域
当程序不执行也就是没有分配处理器时间的时候,如线程处于Sleep/Blocked状态时,线程无法响应虚拟机的中断请求。引入安全区域。
点->面,安全区域就是安全点的扩展。
安全区域:在某一段代码片段中,引用关系不会发生变化,在安全区域中任意地方开始的垃圾收集都是安全的。
当用户线程执行到安全区域时,首先标识自己已经进入安全区域,当虚拟机进行垃圾收集的时候,就不会关注这些已经进入到安全区域的线程。当线程要离开安全区域的时候,它会检查虚拟机是否完成了根节点枚举,完成了线程继续执行,否则线程一直等待,直到根节点枚举结束。