浅谈 Java 之 GC

阅读本文假设你对java内存模型已有一些了解。

1、Java虚拟机中哪些内存需要回收?

  先来看看jvm内存模型,如下图

   

  线程隔离的区域随线程而生,随线程而灭;程序计数器可保存着虚拟机字节码指令的地址(可以看做是当前线程所执行的字节码的行号指示器);栈中的栈帧(与方法关联)随着方法的进入和退出执行压栈出栈操作。既然每一个栈帧与方法关联,那每个栈帧分配多少内存基本上在类结构确定下来就已知。可以看出线程隔离的区域内存的分配和回收具有确定性,当方法结束或线程结束时,内存自然就进行回收了,不需要我们去考虑回收的问题。而在java堆和方法区来说,一个接口可能有多个实现类,我们只有在程序运行期间才知道创建了哪些对象,也即在运行时动态分配内存,并且是否回收是根据对象是否还可用进行回收判断的,java的GC关注的就是这部分的内存。

2、什么时候回收? 

  当对象已经“死亡“(即不可能再被任何途径使用的对象)。

  那么判断对象是否存活的算法有哪些?

       (1)引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它,计数器加1;引用失效时,计数器减1,任何计数器为0的对象就是不可能再被使用的。该算法实现简单,判断效率高,但是Java虚拟机里并没有选用该算法来管理内存,因为该算法很难解决对象相互循环引用问题(相互引用的对象除了它们俩之间没有其他别的引用)。

       (2)可达性分析算法:通过一系列的“GC Roots”作为对象的起点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即不可达),则该对象是不可用的。如下图对象5、6为可回收对象。

  

  

  在Java中,可作为GC Roots 的对象包括以下

    1) 虚拟机栈(栈帧中的本地变量表)中引用的对象;

    2) 方法区中类静态属性引用的对象;

    3) 方法区中常量引用的对象;

    4) 本地方法栈中JNI(Java Native Interface)引用的对象。

  但是,请注意,不可达对象并非是“非死不可”,宣告对象“死亡”要经过至少两次标记过程,没有引用链到达GC Roots标记第一次,并进行筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有重写finalize()方法时或finalize()已被虚拟机调用过,虚拟机将这两种情况视为没有必要执行。Finalize() 方法是对象自我拯救的最后机会,资源重新与引用链上的任何一个对象建立关联,在第二次标记它将被移除出即将回收的集合。

  方法区上的回收时机

  垃圾回收包含两部分内容:废弃常量和无用的类。

  废弃常量:与java堆对象回收相似,没有再被任何对象引用,就会被回收;

  无用的类:满足以下条件,即为无用类,可被回收。

    1) 该类的所有实例都已经被回收,java堆中不存在该类的任何实例;

    2) 加载该类的ClassLoader已经被回收;

    3) 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

3、垃圾回收算法

  (1)标记-清除算法:算法分为“标记”和“清除”两个阶段,首先标记出所有要回收的对象,然后统一回收被标记的对象,由于死亡的对象大部分不是连续的,该算法会较多的内存碎片,在下一次对较大对象分配内存时可能会因为无法分配足够大的连续空间而再次进行垃圾回收;

       (2)复制算法:将内存进行分区,一块区域用完就进行垃圾回收,把存活对象复制到另一块区域,然后清空原来区域。在新生代中就是使用复制算法,把内存以8:1:1分配,分别称为Eden,From Survivor,To Survivor,至于为什么要8:1:1分配,可以参考以下两个链接  http://ifeve.com/jvm-yong-generation/   https://dsxwjhf.iteye.com/blog/2201687

       (3)标记-整理算法:与标记清理相似,但后续不是直接清理而是先移到一端后在进行清理,避免了该内存里的内存碎片产生。

       (4)分代回收算法:新生代使用复制算法,老年代使用标记-整理算法

 

 

书暂时看到这部分,下次再来讲讲垃圾回收算法在jvm的基本实现及具体的垃圾回收器。

posted @ 2019-05-05 10:47  X_huang  阅读(182)  评论(0编辑  收藏  举报