乐趣区

关于java:JVMGC垃圾回收机制

学习 JVM 垃圾回收机制次要学习以下几点:哪些内存须要回收(判断对象能够回收)、什么时候回收(GC 什么时候执行)、怎么回收(垃圾回收算法、垃圾回收器)、垃圾回收过程

JVM GC 回收哪些区域内的垃圾?

JVM GC 只回收 堆区和办法区 内的对象,不回收虚拟机栈内的数据,栈内数据在超出作用域后会被 JVM 主动开释掉。

因为 JVM GC 回收堆区的对象,所以先理解学习一下堆内存的结构图:

堆内存分为 年老代(Young Generation)老年代(Old Generation),年老代和老年代所占空间比例默认是 1:2。年老代又分为EdenSurvivor区,Survivor区由 FormSpaceToSpace组成。Eden 区占大容量,Survivor 两个区占小容量,默认比例是8:1:1。From 和 To 次要是为了解决内存碎片化。

JVM GC 怎么判断对象能够回收?

  • 对象没有援用。
  • 作用域产生未捕捉异样。
  • 程序在作用域失常执行结束。
  • 程序执行了System.exit()
  • 程序发生意外终止(被杀过程等)。

判断对象是否能够回收波及到垃圾回收算法,前面咱们会详情阐明。

有些时候咱们能够把相干的对象设置成 null 来试图显示的革除缓存,然而并不是设置 null 就肯定被标记成能够回收,比方以下代码:

public static void testGC() {ReferenceCountingGC objA = new ReferenceCountingGC(); 
    ReferenceCountingGC objB = new ReferenceCountingGC(); 
    objA.instance = objB; 
    objB.instance = objA; 
    objA = null; 
    objB = null; 
    // 假如在这行产生 GC,objA 和 objB 是否能被回收?System.gc();}

objAobjB设置 null 不会被标记成能够回收因为 objAobjB循环依赖援用关系,然而 System.gc(); 会执行 Full GC 回收。

将对象设置 null 至多没有什么害处,然而 System.gc(); 便不可取了,因为应用 System.gc(); 的时候并不是马上执行 GC 操作,而是会期待一段时间,甚至不会执行,如果被执行会触发 Full GC 是十分影响性能的。

JVM GC 什么时候执行?

Eden 区空间不够贮存对象的时候会执行 Minro GC。升到老年代的对象大于老年代残余的空间时执行Full GC, 或者小于的时候被HandlePromotionFailure 参数强制 Full GC。JVM GC 调优次要是缩小Full GC 的触发次数,能够通过设置参数 NewRatio 管制年老代和老年代所占内存比例,通过设置参数 MaxTenuringThreshold 扭转对象进入老年代的阙值。Full GC十分损耗性能,执行工夫大略是 Minro GC 的 10 倍。

JVM GC 按代的垃圾回收机制

年老代:绝大多数新创建的对象都是被调配在年老代(对象很大的话可能被调配在老年代),年老代触发 GC 对象被回收的过程称之为Minor GC

老年代:对象在年老代周期存活了下来,会被拷贝到老年代,老年代触发 GC 对象被回收的过程称之为Full GC

长久代:也被叫做办法区,用于保留类加载信息、常量、动态变量等,办法区不是用于贮存老年代存活下来的对象,这个区域也可能产生 GC,办法产生 GC 的过程被称为Major GC,办法区产生 GC 的条件十分刻薄,必须满足以下三个条件才会回收:

  • 所有实例被回收。
  • 加载该类的 ClassLoader 被回收。
  • Class 对象无奈通过工作路径拜访(蕴含反射)。

老年代如何解决援用年老代对象问题?

老年代中存在一个 card table,大小为 512 字节,用于寄存所有老年代对象执行年老代对象的援用,当针对年老代执行 GC 的时候,只需查问一下card table 来决定是否回收,而不同查问整个老年代。

垃圾回收过程

  1. 绝大数刚刚新建的对象都会贮存在年老代的 Eden 区。
  2. 当 Eden 区空间有余时就会执行 GC,在执行第一次 GC 之后存活的对象就会挪动到 Survivor 的 From 区。
  3. 尔后每次 Eden 区执行 GC,存活的对象都会被寄存在 From 区。
  4. 当 From 区空间饱和时,在存活的对象就会被挪动到 to 区,而后清空 from 区。
  5. 在以上步骤反复 N 次(N=MaxTenuringThreshold 年龄阙值默认 15)仍然存活的对象就会移到老年代,如果这个时候老年代没有空间了就会触发Full GC,如果触发 Full GC 之后空间还是有余就会抛出 OOM 异样。

JVM GC 外围参数

-XX:NewRatio 
–XX:SurvivorRatio 
–XX:NewSize 
–XX:MaxNewSize

-XX:NewRatio示意年老代和老年代绝对的比例,比方 -XX:NewRatio=2 示意老年代是年老代的 2 被,老年代占堆的 2 /3,年老代占 1 /3。

-XX:SurvivorRatio示意年老代外面 Eden 区和 Survivor 区相比比例,比方 -XX:SurvivorRatio=8 示意Eden:From:To = 8:1:1。SurvivorRatio 不能设置过大也不能设置过小,个别默认值即可。

-XX:NewSize示意年老代的初始化大小。

-XX:MaxNewSize示意年老代最大大小。

JVM GC 算法

根搜索算法

程序把所有援用关系看作一棵树,从一个根节点 GC ROOT 开始寻找对应的援用节点,找到这个节点后持续寻找这个节点的援用节点,当所有节点寻找结束之后,没有被援用的节点就是无用的节点。

上图红色就是无用的节点,能够被回收。

目前 Java 中能够作为 GC ROOT 的对象有:

  • 虚拟机栈、本地办法栈中援用的对象,对象别离是本地变量表、Native 对象。
  • 办法区中动态变量、常量援用的对象。

标记 - 革除算法

标记 - 革除算法采纳从根汇合进行扫描,对存活的对象进行标记,标记结束之后再扫描整个空间中未被标记的对象进行回收。

标记 - 革除算法不须要进行对象的挪动,并且仅对不存活的对象进行回收,在存活的对象比拟多的状况下极为高效,然而因为标记 - 革除算法间接回收不存活的对象,没有对还存活的对象进行整顿,所以会导致内存碎片化。

复制算法

复制算法将内存空间划分为两个区间,所有对象都只会调配在其中一个流动区间,而另外一个区间则是闲暇的。

复制算法采纳从根汇合扫描,将存活的对象复制到闲暇区间,当扫描结束之后,会将流动区间一次性回收,此时本来的闲暇区间变成了流动区间,下次 GC 的时候又反复此操作。复制算法在存活对象比拟少的时候极为高效。

标记 - 整顿算法

标记 - 整顿算法采纳标记 - 革除算法一样的形式进行对象的标记、回收,然而在回收不存活对象占用的空间后,会将所有存活的对象往左挪动,并更新对应的指针,解决了内存碎片的问题。

JVM 为了优化内存的回收,应用分代回收的形式,年老代内存回收采纳复制算法,老年代回收大多采纳标记 - 整顿算法。

垃圾回收器

年老代回收器

Serial:

  • 算法:复制算法
  • 阐明:简略高效的单核机器,Client 模式下默认的年老代收集器。

ParNew

  • 算法:复制算法
  • 阐明:Serial 的多线程版本,运行在 Server 模式下的 JVM 首选的新生代收集器。

Parallel Scavenge

  • 算法:复制算法
  • 阐明:又被称为吞吐量优先收集器,和 ParNew 收集器相似,指标在于达到可管制吞吐量。

老年代回收器

Serial Old

  • 算法:标记 - 整顿算法
  • 阐明:性能个别,单线程版本,在 JDK1.5 及之前的版本中与 Parallel Scavenge 收集器搭配应用,作为 CMS 收集器的后备预案。

Parallel Old

  • 算法:标记 - 整顿算法
  • 阐明:GC 多线程并行,为了代替 Serial Old 与 Parallel Scavenge 配合应用。

CMS

  • 算法:标记 - 革除算法
  • 阐明:对 CPU 资源敏感,进展工夫长。会产生内存碎片,能够通过参数开启碎片的合并整顿。根本已被 G1 取代。

年老代、老年代共用回收器

G1

  • 算法:标记 - 整顿算法
  • 阐明:新的垃圾回收器,既能够回收新生代也能够回收老年代,实用于多核大内存机器、GC 多线程并行执行,进展低、高回收率。
退出移动版