判断对象存活
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1,引用失效时,计数器就减1;任何时刻计数器都为0的对象就是不可能再被使用的。
问题:无法解决对象之间的相互循环引用
根搜索算法(java,c#,List)
通过一系列的名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索通过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
在java中,GC Roots对象包括
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)的引用的对象
引用
- 强引用
只要强引用还在,就不会被回收
"Object obj = new Object()"
- 软引用
在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围并进行第二次回收。
- 弱引用
被弱引用关联的对象只能生存到下一次垃圾收集之前
- 虚引用
一个对象是否有虚引用不对其生存时间构成影响
垃圾收集算法
多个算法一起工作
分代收集算法
根据对象的存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代。新生代适合选用复制算法,老年代对象存活率高,没有额外空间分配担保,就必须使用“标记-清理”或“标记--整理”
复制算法
将可用内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间清理掉
在虚拟机的实现中,将新生代分为一块较大的Eden空间和两块较小的Survior空间(8:1:1),每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性的拷贝到另外一块Survivor,最后清理掉Eden和刚才的Survivor.
- 内存分配
- 对象优先在Eden分配,一次gc后进入Survivor
- 大对象直接进入老年代
避免Eden与两个Survivor区之间发生大量的内存拷贝
- 长期存活的对象将直接进入老年代
对象年龄计数器,经过15次gc而不死
标记-清除
首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象
问题:
效率低,产生大量内存碎片
标记-整理算法
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
垃圾收集器
Serial | Serial Old | ParNew | Parallel Scavenge | Parallel Old |
单线程 | 单线程 | 多线程 | 多线程 | 多线程 |
新生代 | 老年代 | 新生代 | 新生代 | 老年代 |
CMS
并发收集,低停顿,标记-清除算法
G1
之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个java堆(包括新生代,老年代)划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。
回收方法区
- 废弃常量
- 无用的类
- 该类所有实例已回收
- 加载该类的ClassLoader已回收
- 该类对应的Class对象没有在任何地方被引用