深入理解Java内存回收机制

JVM通过GC(Garbage Collection,垃圾回收器)来回收堆和方法区中的内存,这个过程是自动执行的。说到Java GC机制,其主要完成3件事:确定哪些内存需要回收;确定什么时候需要执行GC;如何执行GC。JVM主要采用回收器的方式实现GC,主要的回收器有引用计数回收器和跟踪回收器。

一、引用计数回收器

(1)引用计数器采用分散式管理方式,通过计数器记录对象是否被引用。当计数器为0时,说明此对象已经不再被使用,可进行回收。如图所示:

深入理解Java内存回收机制_第1张图片

(2)引用计数器需要在每次对象赋值时进行引用计数器的增减,所以有一定消耗。另外,引用计数器对于循环引用的场景没有办法实现回收。例如在上面的例子中,如果Object_2和Object_3互相引用,那么即使Object_1释放了对Object_2和Object_3的引用,也无法回收Object_2、Object_3,因此对于java这种会形成复杂引用关系的语言而言,引用计数器是非常不适合的。

二、跟踪回收器

      跟踪收集器采用的是集中式的管理方式,会全局记录数据引用的状态。它基于特定的触发条件(例如定时、空间不足时),执行时需要从根集合来扫描对象的引用关系,这可能会造成应用程序暂停。主要有复制(Copying)标记-清除(Mark-Sweep)和标记-压缩(Mark-Compact)三种实现算法。
  1. 复制(Copying)
      复制采用的方式为从根集合扫描出存活的对象,并将找到的存活对象复制到一块新的完全未被使用的空间中,如图所示:

深入理解Java内存回收机制_第2张图片

复制收集器方式仅需要从根集合扫描所有存活对象,当要回收的空间中存活对象较少时,复制算法会比较高效(年轻代的Eden区就是采用这个算法),其带来的成本是增加一块空的内存空间以及需要进行对象的移动


2. 标记-清除(marking-delete)
       标记-清除采用的方式是从根集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未标记的对象,并进行清除,标记和清除过程如下图所示:

深入理解Java内存回收机制_第3张图片

上图中蓝色的部分是有被引用的存活的对象,褐色部分没被引用的可回收的对象。在标记阶段为了标记对象,所有的对象都会被扫描一遍,扫描的过程是比较耗时的。

深入理解Java内存回收机制_第4张图片

        清除阶段回收的是没有被引用的对象,存活的对象被保留。内存分配器会持有空闲空间的引用列表,当有分配请求时会查询空闲空间引用列表进行分配。标记-清除动作不需要进行对象移动,只需要对不存活的对象进行处理。在空间中存活对象较多的情况下较为高效,但由于标记-清除直接回收不存活对象占用的内存,因此会造成内存碎片


3. 标记-压缩(Mark-Compact)
      标记-压缩和标记-清除一样,是对活的对象进行标记,但是在清除后的处理不一样,标记-压缩在清除对象占用的内存后,会把所有活的对象向左端空闲空间移动,然后再更新引用其对象的指针,如下图所示:

深入理解Java内存回收机制_第5张图片

很明显,标记-压缩在标记-清除的基础上对存活的对象进行了移动规整动作,解决了内存碎片问题,得到更多连续的内存空间以提高分配效率,但由于需要对对象进行移动,因此成本也比较高。

4. 分代式垃圾回收

 分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是是分而治之,根据对象存活的生命周期将内存划分为若干个不同的区域,再根据不同区域的特点来具体选择合适的垃圾回收方案。一般情况下,根据对象易产生垃圾的状态或者对象的大小,将堆区划分为老年代(Tenured Generation)和新生代(Young Generation)。堆内存分代策略如图所示。

深入理解Java内存回收机制_第6张图片

 

(1)新生代也称Young区。新生代划分为一块较大的Eden空间(伊甸园)和Survivor空间(幸存者区)。eden区也称伊甸园(《圣经》中亚当和夏娃出生的地方),很形象的比喻对象的出生地点,全部的新生对象都会出现在eden区。在Survivor 幸存者空间中又分为from和to两块(也称为s0和s1),用于相互复制对象来进行垃圾清理。新生代的绝大部分对象有朝生熄灭的特点,存活率很低,每次垃圾回收时都有大量的对象需要被回收。目前大部分垃圾收集器对于新生代都采取复制回收算法,因为新生代中的对象创建时间不长,更新比较快。每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少。当进行回收时,将Eden和其中一块Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间,如此循环下去。

(2)老年代也称Old区。老年代存放的是一些“大对象”以及新生代中经过多次垃圾回收仍然存活的对象。老年代中对象的生命周期较长,对象存活率较高。每次垃圾收集时只有少量对象需要被回收,产生垃圾少。针对这种情况,使用标记-压缩算法回收效率比较高。

(3)持久代也称Permanent区或方法区。存储类信息、常量、静态变量以及方法描述等信息。相对而言,垃圾回收行为在这个区域是比较少出现的,但并非数据进入了持久代就可以永久存在。对永久代的回收主要回收两部分内容:废弃常量和无用的类

(4)分代垃圾回收过程如图所示 

深入理解Java内存回收机制_第7张图片

 

你可能感兴趣的:(深入理解JVM,Java修行之路)