[Java虚拟机]垃圾回收GC算法
文章目录
在jvm中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此内存垃圾回收主要集中于java堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
一、判断对象是否存活的JVM两种计数算法
1. 引用计数算法
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。
缺点:引用计数算法无法解决对象相互循环引用的问题。
2. 可达性分析计数算法(根搜索算法)
实际开发语言比如java、C#等都是采用可达性分析计数算法判断对象是否存活
可达性分析(Reachability Analysis):从GC Roots
开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
GC Roots主要包括:
1、对象的引用,位于虚拟机栈中。
2、方法区中的静态引用。
3、本地方法栈中JNI的引用。【JNI一般指的是Native方法】
简单地说,GC Root 就是经过精心挑选的一些引用
可达性分析图:
这个过程中在finalize
对象会有一次自己拯救自己的方式,如果再次不拯救自己进入幻象中,那么就可以让其真正的进行回收。
- 强引用 也就是强可达:该对象可以让不同的线程通过各种引用访问的情况,简单来说就是我们新建的对象,在该线程中属于强可达状态
- 软可达 : 只能通过软引用来达到的对象状态
- 弱可达:弱引用状态的对象可达的装填
- 幻象可达: 经过finalize之后的对象状态。没有引用能指向该对象
- 不可达状态:对象可以清楚了。
二、垃圾收集算法
1. 标记清除算法复制算法
其分为两个阶段:标记阶段和清除阶段。
- 在标记阶段,标记所有由 GC Root 触发的可达对象。此时,所有未被标记的对象就是垃圾对象。
- 在清除阶段,清除所有未被标记的对象。
- 问题就是空间碎片问题。如果空间碎片过多,则会导致内存空间的不连续。
2. 复制算法(Copying)
核心思想是将原有的内存空间划分为大小相等的两块,每次只使用一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中。之后清除正在使用的内存块中的所有对象,之后交换两个内存块的角色,完成垃圾回收。
复制算法的缺点是要将内存空间折半,极大地浪费了一半内存空间。现在的商业虚拟机都使用这种复制算法来进行新生代的垃圾收集!因为在对象存活率高的时候,复制算法就显得效率低下
3. 标记-整理算法(Mark-Compact)
是标记清除算法的优化版,分为标记阶段、整理阶段。
- 标记阶段和标记清除算法中的标记阶段是一样的。
- 整理阶段,其则是将所有存活的对象整理在内存的一边,之后清理边界外的所有空间。
- 标记-整理算法(Mark-Compact)不会产生内存碎片,但是会多花点时间用在整理(Compact)上面。
4. 分代收集算法(Generational Collection)
把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
- 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那新生代选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
- 老年代中因为对象存活率高、没有额外空间对它进行分配担保,老年代就使用“标记-清理”或“标记-整理”算法来进行回收。
5. 四种回收算法总结
标记清除算法
分为标记阶段和清除两阶段。标记清除算法最大的问题就是空间碎片问题。比较适合在存活对象比较多的情况。
复制算法(Copying)
复制算法的缺点是浪费了一半内存空间。现在的商业虚拟机都使用这种复制算法来进行新生代的垃圾收集!因为在对象存活率高的时候,复制算法就显得效率低下比较适合存活对象比较少的情况。
标记整理算法(Mark-Compact)
标记整理算法是标记清除算法的优化版,标记-整理算法(Mark-Compact)不会产生内存碎片,但是会多花点时间用在整理(Compact)上面!
分代收集算法(Generational Collection)
新生代选用复制算法,老年代使用“标记-清理”或“标记-整理”算法来进行回收。