《深入理解Java虚拟机》:垃圾收集器与内存分配策略

《深入理解Java虚拟机》:对象创建、布局和访问全过程
如何判断对象已死?引用计数法:每个对象维护一个引用计数器 , 当一个对象被引用 , 计数器加1 , 当引用失效时 , 计数器减1 , 任何时刻计数器为0的对象是不可能被引用的 。
引用计数法的最大问题是 , 它很难解决对象之间的相互循环引用问题 。 如下代码 , objA和objB已经没有外部引用了 , 但是互相引用 , 在引用计数法判断对象是否已死的情况下 , objA和objB计数器一直都是1:
public class ReferenceCounttingGc{ private Object instance = null ; private static final int _1MB = 1024 * 1024 ; public static void main(String[] args) {ReferenceCounttingGc objA = new ReferenceCounttingGc();ReferenceCounttingGc objB = new ReferenceCounttingGc();objA.instance = objB;objB.instance = objA;objA = null;objB = null;System.gc(); }}可达性分析算法:这种算法解决了引用计数法无法解决的相互引用问题 。 它最重要的一个概念就是根对象 , 即GC Roots 。
当一个对象到GC Roots没有任何引用链相连时 , 则证明此对象不可用 。 如图所示 , object5、object6、object7虽然互相关联 , 但是到GC Roots不可达 , 所以它们会被判定为可回收的对象 。
《深入理解Java虚拟机》:垃圾收集器与内存分配策略文章插图
哪些可以称之为根对象呢 , 主要是指那些肯定不会被垃圾回收掉的对象 , 比如栈、方法区、本地方法栈中的对象:
虚拟机栈中引用对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
【《深入理解Java虚拟机》:垃圾收集器与内存分配策略】本地方法栈中JNI引用的对象 。
回收方法区:垃圾回收主要在java堆内存 , 其实永久代也有垃圾回收 , 只是回收“性价比”一般比较低 。 收集主要回收两部分内容:废弃常量和无用的类 。
废弃常量回收与堆对象回收非常类似 , 假如一个字符串“abc”在常量池 , 但是当前系统 , 没有任何一个String对象是叫做“abc”的 , 如果这时发生垃圾回收 , 这个“abc”常量就会被清理出常量池 。
判断“无用的类” , 一般需要同时满足以下3个条件:

  1. 该类所有的实例都已经被回收 , 也就是java堆内存不存在该类的任何实例;
  2. 加载该类的ClassLoader已经被回收;
  3. 该类对应的java.lang.Class对象没有在任何地方被引用 , 无法在任何地方通过反射访问该类的方法 。
垃圾收集算法标记-清除:算法分两阶段 , 首先标记出需要回收的对象 , 然后统一回收标记过的对象 。
它的主要缺点 , 一个是效率问题 , 标记-清楚两个过程效率都不高;二是内存碎片过大 , 程序运行期间需要分配大大对象时 , 无法找到连续的内存空间不得不提前触发领另一次垃圾收集动作 。
《深入理解Java虚拟机》:垃圾收集器与内存分配策略文章插图
复制算法:它将内存分为大小相等的两块 , 每次只用其中一块 , 当这一块内存用完 , 就将还存活的对象复制到另外一块上面 , 然后再把使用的内存空间一次清理掉 。 hotspot的新生代就是采用这种复制算法 , 在from和to区来回复制 。
它的优点是内存空间连续;缺点是要浪费一块空内存间 , 且存活对象过多的情况下 , 复制效率低下 。
标记-整理算法:复制算法在对象存活率比较高的情况下复制效率低下 , 而且还浪费50%的内存空间 , 于是有人提出了基于标记-清除算法的优化的算法——标记-整理 。
标记-整理算法的清除不是直接对可回收对象进行清除 , 而让所有存活对象都向一端移动 , 然后清理掉另一端的内存 。


推荐阅读