所谓垃圾就是内存中已经没有用的对象。 既然是”垃圾回收",那就必须知道哪些对象是垃圾。Java 虚拟机中使用一种叫做"**可达性分析”**的算法来决定对象是否能够被回收。算法
可达性分析算法是从离散数学中的图论引入的,JVM 把内存中全部的对象之间的引用关系看做一张图,经过一组名为”GC Root"的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,最后经过判断对象的引用链是否可达来决定对象是否能够被回收。以下图所示:数组
好比上图中,对象A/B/C/D/E 与 GC Root 之间都存在一条直接或者间接的引用链,这也表明它们与 GC Root 之间是可达的,所以它们是不能被 GC 回收掉的。而对象M和K虽然被对J 引用到,可是并不存在一条引用链链接它们与 GC Root,因此当 GC 进行垃圾回收时,只要遍历到 J/K/M 这 3 个对象,就会将它们回收。markdown
注意:上图中圆形图标虽然标记的是对象,但实际上表明的是此对象在内存中的引用。包括GC Root也是一组引用而并不是对象。并发
在Java中,有如下几种对象能够做为GC Root:
1.Java 虚拟机栈(局部变量表)中的引用的对象。
2.方法区中静态引用指向的对象。
3.仍处于存活状态中的线程对象。
4.Native 方法中 JNI 引用的对象。性能
1.Allocation Failure:在堆内存中分配时,若是由于可用剩余空间不足致使对象内存分配失败,这时系统会触发一次 GC。
2.System.gc():在应用层,Java 开发工程师能够主动调用此 API 来请求一次 GC。spa
从”GC Roots”集合开始,将内存整个遍历一次,保留全部能够被 GC Roots 直接或间接引用到的对象,而剩下的对象都看成垃圾对待并回收,过程分两步。线程
优势:实现简单,不须要将对象进行移动。
缺点:这个算法须要中断进程内其余组件的执行(stop the world),而且可能产生内存碎片,提升了垃圾回收的频率。3d
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中。以后,清除正在使用的内存块中的全部对象,交换两个内存的角色,完成垃圾回收。调试
须要先从根节点开始对全部可达对象作一次标记,以后,它并不简单地清理未标记的对象,而是将全部的存活对象压缩到内存的一端。最后,清理边界外全部的空间。所以标记压缩也分两步完成:日志
Java 虚拟机根据对象存活的周期不一样,把堆内存划分为几块,通常分为新生代、老年代,这就是 JVM 的内存分代策略。注意: 在 HotSpot 中除了新生代和老年代,还有永久代
分代回收的中心思想就是:对于新建立的对象会在新生代中分配内存,此区域的对象生命周期通常较短。若是通过屡次回收仍然存活下来,则将它们转移到老年代中。
新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集通常能够回收 70%~95% 的空间,回收效率很高。新生代中由于要进行一些复制操做,因此通常采用的 GC 回收算法是复制算法。
新生代又能够继续细分为 3 部分:Eden、Survivor0(简称 S0)、Survivor1(简称S1)。这 3 部分按照 8:1:1 的比例来划分新生代。这 3 块区域的内存分配过程以下:
绝大多数刚刚被建立的对象会存放在 Eden 区。如图所示:
当 Eden 区第一次满的时候,会进行垃圾回收。首先将 Eden区的垃圾对象回收清除,并将存活的对象复制到 S0,此时 S1是空的。如图所示:
下一次 Eden 区满时,再执行一次垃圾回收。这次会将 Eden和 S0区中全部垃圾对象清除,并将存活对象复制到 S1,此时 S0变为空。如图所示:
如此反复在 S0 和 S1之间切换几回(默认 15 次)以后,若是还有存活对象。说明这些对象的生命周期较长,则将它们转移到老年代中。如图所示:
一个对象若是在新生代存活了足够长的时间而没有被清理掉,则会被复制到老年代。老年代的内存大小通常比新生代大,能存放更多的对象。若是对象比较大(好比长字符串或者大数组),而且新生代的剩余空间不足,则这个大对象会直接被分配到老年代上。
咱们可使用 -XX:PretenureSizeThreshold 来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。老年代由于对象的生命周期较长,不须要过多的复制操做,因此通常采用标记压缩的回收算法。
注意:对于老年代可能存在这么一种状况,老年代中的对象有时候会引用到新生代对象。这时若是要执行新生代 GC,则可能须要查询整个老年代上可能存在引用新生代的状况,这显然是低效的。因此,老年代中维护了一个 512 byte 的 card table,全部老年代对象引用新生代对象的信息都记录在这里。每当新生代发生 GC 时,只须要检查这个 card table 便可,大大提升了性能。
为了让上层应用开发人员更加方便的调试 Java 程序,JVM 提供了相应的 GC 日志。在 GC 执行垃圾回收事件的过程当中,会有各类相应的 log 被打印出来。其中新生代和老年代所打印的日志是有区别的。
注意:在有些虚拟机实现中,Major GC 和 Full GC 仍是有一些区别的。Major GC 只是表明回收老年代的内存,而 Full GC 则表明回收整个堆中的内存,也就是新生代 + 老年代。
虚拟机垃圾回收机制不少时候都是影响系统性能、并发能力的主要因素之一。尤为是对于从事 Android 开发的工程师来讲,有时候垃圾回收会很大程度上影响 UI 线程,并形成界面卡顿现象。所以理解垃圾回收机制并学会分析 GC Log 也是一项必不可少的技能。