一、引用计数法
1.原理
统计每一个对象被引用的次数,如果引用次数为0就释放对象。能立即回收无用内存。
2.实现
当一个对象要重新赋值引用时:
- 把新对象引用计数+1
- 老对象引用计数-1
- 赋值
伪代码:
image.png
3.存在的问题
- 并发场景下,对引用计数的修改需要和对象指针的修改保证同步,往往需要加锁或者复杂的无锁算法
- 有时会引发连锁式的回收
- 无法有效解决循环引用
注意:要先加,再减,否则如果刚好减到0的话就会被回收了。
二、复制算法
复制算法.png1.原理
把程序运行的堆分成大小相同的两半,一半为from空间,一半为to空间。利用from空间进行分配,当空间不足以分配对象的时候,触发GC。GC会把存活的对象全部复制到to空间。复制完成以后,会把from和to互换。
2.特点
- 1.分配采用bump the pointer,每次都把top指针向后移动即可。复制的存活对象多大,指针就移动多大。
- 2.回收是否高效取决于存活对象的比例。存活对象越少,效率越高
- 3.无内存碎片
- 4.需要浪费一半内存空间
- 5.需要停顿
- 6.实现简单
在整理的过程 需要停顿业务线程,因为在整理对象的过程,指针会发生改变。
3.对象位置发生变化,指向的引用维护方法
1⃣️引入中间层
虽然在复制的过程中变得简单,但是中间层的分配和回收并不容易做;而且每次访问对象属性都变成了再次访问,性能的退化也是不能接受的。
2⃣️使用forwarding指针
1.A复制到to空间
2.因为A指向着C,所以C也直接复制到to空间,修改C的引用,让A指向C'
3.B复制到to空间,但是B的指针还是指向的from空间的C;
4.在第二步C复制到to空间时,让C指向新的C'地址(forwarding指针)。
5.B从C中的对象头中拿到forwarding,指向新的C'。
4.提高空间利用率
将Eden空间分配成Eden,Survivor0和Survivor1区域。这样Survivor空间的浪费就可以减少了。
配置Survivor空间大小是JVM GC调参中的重要参数。
例如 -XX:SurvivorRatio
=8 代表Eden:S0:S1=8:1:1
from:S1+Eden
to:S0
第一次:把s1+Eden一起经过回收存活的放入S1;
from:S0+Eden
to:S1
第二次:把S0+Eden一起经过回收存活的放入S0;
浪费的空间就只有S0或者S1的大小。
三、标记清除法
标记清除法.png1.原理
使用链表管理所有的空闲区域。在Mark阶段(标在对象头),将所有的存活对象识别出来,将不存活的对象所占用的内存还给链表。
2.特点
- 1.分配和回收都要操作链表
分配要查询链表哪个位置可以放得下这个对象,回收再将内存还给链表 - 2.有内存碎片
- 3.总体的内存空间利用率较高
- 4.可以用很小的代价实现并发标记和清除(在标记的过程中对象指针不会发生变化,不需要停止业务线程)
极客时间《Java面试冲刺班之JVM难点攻克》学习笔记Day21
网友评论