Fork me on GitHub

垃圾回收机制

1.什么时候

堆是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象。JVM中的堆一般分为三个部分:新生代、老年代、永久代

堆的内存模型
enter image description here

JVM中每次只会使用Eden区和其中一块survivor区域来为对象服务,所以无论什么时候总有一块survivor区域空闲着。因此新生代实际可用内存空间为90%的新生代空间。

  1. 新生代

主要用来存放新生的对象,一般占据堆得1/3空间。由于频繁创建对象,索引新生代会频繁发出minor gc进行垃圾回收。新生代又被划为三个区:Eden区、from survivor、to survivor。Eden区:java新生对象的出生地(如果新创建的对象占用内存很大,则直接分配到来年代)。当Eden区内存不够的时候会触发minor gc,对新生代代区域进行一次垃圾回收。

Minor GC的过程:采用复制算法,首先把Eden区和from survivor区域中存货的对象复制到to survivor区域,然后再把Eden区和from survivor区域清除。to survivor区域将这些存活的对象年龄+1(如果to survivor不够位置了就放到老年区)。以后对象在survivor区域中每熬过一次gc则年龄+1,当年龄到达某个值(默认为15),这些对象就会成为老年代。最后from survivor 和 to survivor互换,原to survivor成为下一次GC时的from survivor区。

  1. 老年代

主要存放应用程序中生命周期长的内存对象,对于一些较大的对象(急需要分配一块较大的连续内存空间),是直接进入老年代的,还有一些是从新生代survivor区域熬过来的对象。

老年代的对象比较稳定,所以 full gc不会频繁执行,升到老年代的对象大于老年代剩余空间时full gc,或者小于时被HandlePromotionFailure参数强制full gc。

Full gc采用标记-清除算法:首先扫描一次所有的老年代,标记处存活对象,然后挥手没有被标记的对象。Full gc耗时长,因为要扫描再挥手,会产生内存碎片,为了减少内存损耗,可采用标记整理法。当老年代也装不下的时候,就会抛出OOM异常。

  1. 永久代

指内存的永久保存区域,主要存放class和原数据信息。类在被加载的时候放入永久代。他和堆区域不一样,gc不会在主程序运行期间对永久代进行清理。这也导致了永久代的区域随着加载类的增多而胀满,最终抛出OOM异常(堆内存溢出)

java8中,永久代已经被移除,被一个称为“元空间”的区域所取代。元空间的本质和永久代类似,都是对jvm规范中方法区的实现。不过元空间和永久代的最大区别在于:元空间不在jvm,而是使用本地内存。默认情况下,元空间的大小仅受本地内存限制。类的元数据放入本地内存,字符串池和静态变量放入java堆中。这样加载多少类的元数据是由系统的实际可用空间来控制的。

采用元空间取代永久代的原因:
A. 为了解决永久代OOM问题,元数据和类对象存放在永久代中,容易出现性能问题和内存溢出。
B. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因此对控件有限,此消彼长)。


2.对什么对象

Java并不采用引用计数法来判定对象是否可用,因为引用计数法不能很好地解决循环引用问题。而是采用根搜索法,思想是通过一系列名为“GC ROOTS”的对象作为起始点,从节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC ROOTS没有任何引用链相连,即从GC ROOTS到这个对象不可达。则证明此对象不可用,可回收。从root搜索不到,且经过第一次标记清理后,仍然没有复活的对象。

可作为GC ROOTS的对象:虚拟机栈中局部变量引用的对象。类静态属性引用的对象。常量引用的对象,JNI中引用的对象。


3.做了什么

主要做了清理对象、整理内存的工作。新生代采用了复制算法,过程如上。复制算法优点:无内存碎片化,浪费可用内存。老年代采用的是标记整理法,首先扫描一次所有老年代,标记处存活对象,然后把存活对象再移动到一端,最后回收没有被标记的对象。标记的过程其实就是遍历所有gc roots,然后将所有gc roots可到达的对象标记为存活对象。缺点:标记和整理过程效率不高,因为还需要移动对象,成本相对较高。优点:不会产生内存碎片化。

垃圾回收算法:

  1. 标记清除法:先标记要回收的对象,再统一进行回收操作。简单方便、内存碎片化严重。
  2. 复制算法:将对分成2部分,一份空白(A),一份存放对象(B),GC后,将B中存活的对象复制到A中,然后B变成空白。
  3. 标记-整理:先标记可回收对象,将存活的对象移动到一端,再回收对象。无内存碎片。成本相对较高
  4. 分代回收:年轻代用复制算法,老年代用标记整理算法。

根据对象存活特性,合理使用回收算法,商业jvm都用分代回收。

既然jvm采用分代回收,name年轻代和老年代各自使用的垃圾收集器也不一样。年轻代使用的垃圾收集器是:Serial,ParNew,Parallel Scavenge。老年代使用的是:CMS,SerialOld,Parallel Old。通用的收集器G1

  1. 串行的,也就是采用单线程(比较老了),分类:serial new(收集年轻代,复制算法)和serial old(收集老年代,标记整理),缺点:单线程,进行垃圾回收时暂时所有的用户线程。优点:实现简单。
  2. 并行的,采用多线程,对于年轻代有两个:ParNew和Parallel scavenge,ParNew回收期间暂停其他所有工作线程。parallel scavenge是一个针对年轻代的垃圾回收器,采用复制算法,主要的优点是进行垃圾回收时不会停止用户线程。Parallel Old是老年代的垃圾回收器,使用多线程,标记整理法。

CMS收集器:已获得最短回收暂停时间为目标,允许垃圾收集线程与用户线程并行。主要有以下4个阶段:

  1. 初始标记,标记GC ROOTS可关联的用户对象,会暂停用户线程。
  2. 并发标记,根搜索过程中,用户线程并行。
  3. 重新标记,由于在并发标记过程中,用户线程导致的对象状态变化,会暂停用户线程。
  4. 并清理,清理可回收对象,用户线程并行。

G1:一款先进的通用垃圾收集器,他针对整个堆,并将其分为大小相等的区域,记录每个区占用信息,优先回收价值更高的区域。

-------------本文结束感谢您的阅读-------------
您的支持将鼓励我继续创作!