Java-GC(Gabage Collection)

首先,引入三个问题,通过这三个问题的步步深入来解决对GC的困惑:

  1. jvm怎么确定哪些对象应该进行回收
  2. jvm会在什么时候进行垃圾回收的动作
  3. jvm到底是怎么清楚垃圾对象的

这里先提到JVM运行时数据区和JVM内存结构:

  • JVM运行时数据区由程序计数器、堆、虚拟机栈、本地方法栈、方法区部分组成,结构图如下所示。
  • JVM内存结构由程序计数器、堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:

Java-GC(Gabage Collection)

1)程序计数器

几乎不占有内存。用于取下一条执行的指令。

2)堆

所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图如下所示:新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象。
Java-GC(Gabage Collection)
3)栈

每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方

法调用过程中的临时变量、参数和中间结果。

4)本地方法栈

用于支持native方法的执行,存储了每个native方法调用的状态

5)方法区

存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用永久代(PermanetGeneration)

来存放方法区,(在JDK的HotSpot虚拟机中,可以认为方法区就是永久代,但是在其他类型的虚拟机中,没有永久代

的概念,有关信息可以看周志明的书)可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。

1. jvm怎么确定哪些对象应该进行回收

对象是否会被回收的两个经典算法,判断对象存活还是死亡的算法(引用计数算法、可达性分析算法)。

引用计数法:

简单的来说就是判断对象的引用数量。实现方式:给对象共添加一个引用计数器,每当有引用对他进行引用时,计数器的值就加1,当引用失效,也就是不在执行此对象是,他的计数器的值就减1,若某一个对象的计数器的值为0,那么表示这个对象没有人对他进行引用,也就是意味着是一个失效的垃圾对象,就会被gc进行回收。  但是这种简单的算法在当前的jvm中并没有采用,原因是他并不能解决对象之间循环引用的问题。假设有A和B两个对象之间互相引用,也就是说A对象中的一个属性是B,B中的一个属性时A,这种情况下由于他们的相互引用,从而是垃圾回收机制无法识别。

重点:无法解决对象间循环引用的问题,因此,目前几乎没有JVM使用这种算法判断对象是否存活;
那么要解决这个问题,就引入了可达性分析算法:

可达性分析算法,通过判断对象的引用链是否可达来决定对象是否可以被回收。可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连(就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。
Java-GC(Gabage Collection)

2.jvm会在什么时候进行垃圾回收的动作

  1. 会在cpu空闲的时候自动进行回收
  2. 在堆内存存储满了之后
  3. 主动调用System.gc()后尝试进行回收(调用System.gc()并不保证一定会进行GC)

3.jvm到底是怎么清楚垃圾对象的

如何回收说的也就是垃圾收集的算法。
算法又有四个:标记-清除算法,复制算法,标记-整理算法,分代收集算法.(编程思想第四版中看到是stop and copy-暂停程序进行GC,以及mark and sweep结合使用进行GC)

  1. 标记-清除算法:这是最基础的一种算法,分为两个步骤,第一个步骤就是标记,也就是标记处所有需要回收的对象,标记完成后就进行统一的回收掉哪些带有标记的对象。这种算法优点是简单,缺点是效率问题,还有一个最大的缺点是空间问题,标记清除之后会产生大量不连续的内存碎片,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而造成内存空间浪费。
  2. 复制算法。 复制将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。只是这种算法的代价是将内存缩小为原来的一半。
  3. 标记-整理算法: 标记整理算法与标记清除算法很相似,但最显著的区别是:标记清除算法仅对不存活的对象进行处理,剩余存活对象不做任何处理,造成内存碎片;而标记整理算法不仅对不存活对象进行处理清除,还对剩余的存活对象进行整理,重新整理,因此其不会产生内存碎片。
  4. 分代收集算法: 分代收集算法是一种比较智能的算法,也是现在jvm使用最多的一种算法,他本身其实不是一个新的算法,而是他会在具体的场景自动选择以上三种算法进行垃圾对象回收。那么现在的重点就是分代收集算法中说的自动根据具体场景进行选择。这个具体场景到底是什么场景。 场景其实指的是针对jvm的哪一个区域,1.7之前jvm把内存分为三个区域:新生代,老年代,永久代。
    Java-GC(Gabage Collection)

得出结论: 1、在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。 2、老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须用标记-清除或者标记-整理。总结:
Java-GC(Gabage Collection)

注意: 在jdk8的时候java废弃了永久代,但是并不意味着我们以上的结论失效,因为java提供了与永久代类似的叫做“元空间”的技术。 废弃永久代的原因:由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryErroy。元空间的本质和永久代类似。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。也就是不局限与jvm可以使用系统的内存。理论上取决于32位/64位系统可虚拟的内存大小。

摘自:
http://www.sohu.com/a/235928510_100123073
https://www.cnblogs.com/honey01/p/9475726.html