jvm gc

jvm gc

一. 小例子说明

1
2
3
void method() {
Object obj = new Object();
}

对于以上代码,会生成2部分内存区域:

  1. obj 这个引用变量,会放到jvm stack 里面。
  2. 真正的object的class实例对象,会放到 heap 里面

当方法执行结束以后,stack里面的变量会马上被回收,而heap上的class实例,则会等待下一次gc的时候被回收 。

二. 垃圾的判断

哪些对象是垃依据什么进行判断的 ?

  1. 引用计数算法
    给对象添加一个计数器 每一次引用 计数器加1,每一次引用失效,计数器减1

    当计数器为0的时候,那么就可以被回收掉

    一个很重要的缺陷就是 ,引用计数算法无法解决循环引用的问题
  2. 根搜索算法
    使用根搜索算法判断对象是否存活,通过一系列被称为‘GC roots’ 的根为起点进行向下搜索,当一个对象到 gc roots 没有任何引用链相连,曾明此对象不可用。

    gc roots 包括:
    1. vm栈中(帧的本地变量)的引用 。
    2. 方法区的静态引用 。 jvm不要求实现,商业垃圾jvm基本都会实现。回收无用类和废弃常量 。
      类的回收需满足如下条件 : 所有的实例都已经被回收 ,
      加载该类的classloader已经被回收 ,
      加载该类的 java.lang.Class对象已经没有在任何对象 被引用,包括反射
    3. jni本地方法引用

三. 常见 GC 算法

  1. 标记清除

    分为【标记】和【清除】 2个阶段,先标记可以被回收的对象,然后再去逐一清除。
    缺点:

    1. 效率低下 2. 会产生不连续的的内存碎片 , 可能会导致后续操作无法找到足够的内存空间而触发二次的gc 。 3. 扫描所有对象,堆越大,时间越长。 gc次数越多,碎片化越严重。
  2. 标记整理

    标记过程和之前是一样的,而后续步骤不是直接进行清理,而是将所以有存活对象一端移动,然后清理掉这这端边界以外的内存。

    相比标记清除,不会产生碎片,但是所需时间更长。

  3. 复制搜集算法

    将可用的内存划分为两块,每次只是用其中的一块,当半区内存用完了,仅仅将存活的对象赋值到另一块上面,然后将原来的空间一次性清除掉

    优点:

    实现简单,高效 不会产生碎片。
    缺点:

    将原来的内存空间缩小为原来的一半,带价也是挺高昂的。在对象存活高的时候,效率下降

    在现代商业vm中常用来回收新生代 。 新生代中划分为 Eden 和 2块较小的survivor ,每次使用 Eden和其中的一块survivor ,当回收时,将Eden和survivor中的存活对象,赋值到另一块啊 survivor中,然后清理 Eden和使用过的survivor。 hotspot中 eden和survivor 默认比例为 8 : 1

  4. 分代算法

    当前商业虚拟机都是采用分代算法来实现,根据对象不同一般划分为如下区域:

    1. java堆分为新生代和老年代,根据各个年代的特点采用不同的收集算法 。
      譬如新生带每次gc都只有少量存活,那么选用复制算法 。
    2. 老年代采用 标记清除或者标记整理算法

      新生成的对象被存放在年轻代 , 年轻代采用复制算法 算法(理论上年轻代的对象生命周期都非常短,适合复制算法)

      年轻代分为三个区域 Eden 和 两个 survivor 区 。对象在Eden生成,当Eden区满了时候,此区域的存活对象会被复制到其中的一个 survivor区域,,当这个survivor区域也满了的时候,会存活的对象复制到另外一个survivor区域,。当第二个survivor也满了的时候,从第一个survivor复制过来的还存活的对象会被放到老年代 。

      老年代存放经过一次或者多次gc还存活的对象,采用 标记清除或者标记整理算法进行gc

      永久代,在jdk 8 中已经被取消。 它并不属于堆,gc也会涉及到这个区域 。它存放了每个class的结构信息,包括常量池,字段描述,方法描述 。

内存分配:

  1. 堆上分配: 大多数情况下,会在Eden生进行分配,偶尔会分配到old上
  2. 栈上分配: 原子类型的局部变量

内存回收:
在 full gc 的时候会对reference类型的引用进行特殊处理

  1. soft: 内存不够时一定会被gc ,长期不用也会被gc
  2. weak: 一定会被gc,当被mark为dead
    ,会在referencequeue中通知
  3. phantom: 本来就没引用,当jvm heap释放 会通知

垃圾收集算法:

  1. young generation: serial, parnewe ,parallel scavenge
  2. old generation: cms , serial old , parallel old

minor gc:
时间短,频繁,复制算法执行效率高,当 Eden满了会触发

full gc:
对整个jvm进行整理。触发的实际: old满了,perm满了,system.gc。 他的效率很低,应当尽量避免

垃圾回收器的并行和并发:

  1. 并行: 多个收集器同事同做,但用户线程处于等待状态

  2. 并发: 在收集器工作的时候,用户线程也可以工作。但是在关键的步骤还是要暂停,比如标记阶段。 在清理阶段用户线程和gc线程可以同时工作 。

  3. serial收集器:会暂停用户线程 , 默认的新生代收集器, 单线程实现。 在新生代采用 复制算法,老年代采用标记算法 。

  4. parNew收集器: serial的多线程版本,使用多线程。其余的行为(对象份额 ,回收策略等)和serial一样

  5. parallel scavenge 收集器: 多线程, 复制算法,吞吐量最大化 允许较长时间的stop the world。

  6. parallel old : jvm 1.6 , 多线程,标记整理算法,注重吞吐量,gc停顿不太理想。