1.如何判断对象可以回收

这里有两种不同的算法。

1.1 引用计数法

只要一个对象被其他变量所引用,那么我们就让这个计数+1。如果引用2次,就+2。
如果一个变量不再引用它了,就-1。当计数变为0的时候,那么这个对象就可以作为垃圾被回收。

但是引用计数法有一个严重的弊端,那就是循环引用。看下图:
QQ图片20200301115001.png
A对象引用了B对象,A的引用计数为1。B对象引用了A对象,B的引用计数为1。除此之外,这两个对象再无任何引用。
事实上,这两个对象已经不可能再被访问。但是因为它们互相引用着对方,导致它们的引用计数都不为0,所以引用计数法无法通知GC收集器回收它们。这样就会造成内存上的泄露。

1.2 可达性分析算法

可达性分析算法首先要确定一系列根对象(GC Roots)

什么是根对象?
先可以这样理解,根对象就是那些肯定不能当成垃圾被回收的对象。

在垃圾回收之前,我们首先会对堆内存中的所有对象进行一遍扫描。然后看看每一个对象是否被刚才提到的根对象直接或间接的引用。如果是,那么这个对象就不能被回收,反之这个对象就可以被作为垃圾回收。
GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。

哪些对象可以被作为GC Roots呢?

  • System Class(系统类)

这些类都是由类加载器加载的类。

  • Native Stack

虚拟机在调用方法时会调用一些操作系统方法,操作系统方法在运行时所引用的java对象肯定时不能回收的。

  • Busy Monitor

正在加锁的对象也可以作为根对象。

  • Thread

活动中的线程肯定不能被当成垃圾回收。线程内每次方法调用都会产生一个栈帧,那么栈帧内所引用的对象就可以当作根对象。

1.3 四种引用

严格来讲,不止4种引用。

  1. 强引用(strong reference)
  2. 软引用(soft reference)
  3. 弱引用
  4. 虚引用
  5. 终结器引用

QQ图片20200301203502.png

  • 强引用(Strongly Reference)是最传统的引用的定义。是指在程序中普遍存在的引用赋值,就是类似 Object obj = new Object( ) 这种引用关系。
  • 软引用(Soft Reference)是用来描述一些还有用但是并非必须的对象。比如图中C对象间接引用了A2对象。对于软引用关联着的对象,在系统发生内存溢出异常之前,会把这些对象列入回收范围进行第二次回收。如果内存还不够,才会抛出内存溢出异常。
  • 弱引用(Weak Reference)也是用来描述非必须对象的。不过它的强度比软引用弱一些。被弱引用关联着的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作后,无论当前内存是否充足,都会回收掉被弱引用关联着的对象。
  • 虚引用(Phantom Reference)是最弱的一种引用关系。一个对象是否有虚引用完全不会对其生存时间构成影响。当然也无法通过虚引用获得对象实例。为对象设置虚引用的唯一目的就是在这个对象被回收时收到一个系统通知。

    2.垃圾回收算法

    2.1 标记-清除算法

    QQ图片20200303155134.png
    第一阶段—标记
    先标记哪些垃圾可以被回收的。(那些没被GC Root引用的对象就是可回收的)
    QQ图片20200303155431.png图中灰色的就是可以回收的对象
    第二阶段—清除
    清除就是把这些垃圾对象所占用的内存空间进行释放。而且只需要记住内存空间的起始和结束地址就可以清楚,所以说速度很快。
    QQ图片20200303155751.png

缺点
产生大量不连续的内存碎片。当要分配一个较大的对象时,无法找到足够的连续内存时,又会触发一次垃圾收集动作,所以说效率不高。

2.2 标记-整理算法

第一阶段—标记
标记阶段跟标记清除算法是一样的做法。
第二阶段—整理
QQ图片20200303161752.png
在整理阶段,不是直接对可回收对象进行清理,而是让所有的存活对象都向一端移动,然后直接清理掉端边界以外的内存。
缺点
整理会牵扯到对象地址的移动,所以效率会不高。

2.3 标记-复制算法

QQ图片20200303163105.png
该算法将可用内存划分为大小相等的两块,每次只使用其中的一块。我们把左边这个区域称为from,把右边这个区域成为to。
首先一样标记出可回收的对象。把from区域存活着的对象复制到to区域,然后把from区域一次性清理掉。最后交换from区域和to区域
QQ图片20200303163423.png
缺点
将可用内存缩小为了原来的一半,空间浪费有点多。

3.分代回收机制

3.1 对象优先在Eden区分配

虚拟机不会单独地采用一种算法,而是灵活地采用几种算法协调工作。具体实现的方法就是分代垃圾回收机制
它把整个堆内存划分了两个区域,一个叫新生代,一个叫老年代

:::info 为什么要把堆内存划分成这两个区域呢?
Java中有的对象需要长时间使用,这些对象就放在老年代中。而那些用完就可以丢弃的对象,就可以放在新生代中。
不同的区域采用不同的算法,会使得垃圾回收的效率更高。 :::

新生代又划分了几个具体区域:Eden区、幸存区from、幸存区to
QQ图片20200303170228.png
当我们创建一个新的对象时,这个新的对象就会被分配到一个叫伊甸园(Eden区)的空间。当Eden区没有足够空间进行分配时,就会触发一次垃圾回收。新生代的垃圾回收我们一般都叫它——Minor GC
PS:Minor GC会引发stop the world,暂停其他用户线程。等GC结束,其他用户线程才恢复运行。
接着就开始标记可回收对象,当标记成功之后,这里就会采用复制算法,将存活的对象复制到幸存区To中。并且让这些存活的对象寿命+1。
QQ图片20200306112127.png
最后交换幸存区From和幸存区To的位置(注意,这里交换的是引用)。这样伊甸园的空间又充足了,又可以放新的对象了。
过一段时间,伊甸园对象又满了,就会触发第二次垃圾回收。这次除了标记伊甸园的可回收对象,也要标记幸存区From区域中的存活对象,然后将存活对象放入幸存区To中,并且寿命加1。最后再和幸存区From区域交换。
QQ图片20200306113043.png

对象不可能一直呆在幸存区中,如果幸存区中的对象寿命超过了一定的阈值(这个阈值一般最大是15次:4个bit),说明这个对象具有更高的价值。那我们就把他晋升到老年代去。

如果老年代也快放满了,这时在创建对象会触发什么呢?
这时会先触发一次Minor GC,如果空间还不足,这时就会触发一次Full GC。对整个内存空间进行一次清理。如果回收完空间仍不足,就会报错OutOfMemory

3.2 长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄计数器。对象在幸存区每熬过一次MinorGC,年龄就增加一岁。当年龄增加到一定程度(默认是15),就会被晋升到老年代。

3.2 相关虚拟机参数

含义 参数 注释
堆初始大小 -Xms
堆最大大小 -Xmx或-XX:MaxHeapSize=size
新生代大小 -Xmn或-XX:NewSize=size +
-XX:MaxNewSize=size
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio和
-XX:+UseAdaptiveSizePolicy
前面的是初始化幸存区的大小,后面是动态比例调整的开关
幸存区比例 -XX:Survivor-Ratio:ratio
晋升阈值 -XX:MaxTenuringThreadsold=threadsold 寿命如果超过一定的阈值,就会从新生代晋升到老年代
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC前MinorGC -XX:+ScavengeBeforeFullGC 是一个开关:是否在FullGC前做一次新生代的MinorGC

3.3 空间分配担保机制

在发生MinorGC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间。如果成立,这一次MinorGC是可以确保安全的。

4.垃圾收集器

4.1 Serial收集器

串行收集器

从名字可以看出,该收集器是一个单线程工作的收集器。更重要的是,在它进行垃圾收集时,必须“stop the world”,也就是暂停其他所有工作线程。

  • 优点:简单而高效。

    4.2 ParNew收集器

    ParNew收集器实质上时Serial收集器的多线程并行版本

4.3 Parallel Scavenge收集器

也被称作吞吐量优先的垃圾收集器

4.3 响应时间优先的垃圾回收器

  • 多线程
  • 堆内存较大的情况,多核CPU
  • 在垃圾回收时,让stop the world的单次时间尽可能地短 0.1 0.1 0.1 0.1 0.1 = 0.5

    4.4 G1垃圾收集器

    G1是一款面向服务端应用的垃圾收集器,目标是用在多核、大内存的机器上,它在大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量。
    微信图片_20200306205220.png
    G1垃圾收集器分为了3个阶段,如图所示。这个过程是一个循环的过程。

1) Young Collection

微信图片_20200306210051.png

G1的内存空间结构跟传统的有所不同,它将整个堆内存空间划分为大小相等的内存区域Region默认是512k)。每个区域都可以是伊甸园,幸存区,老年代。这些区域在物理内存地址上是不连续的,但在逻辑上是连续的。

当伊甸园的内存空间用完时,就会触发一次新生代回收(Young Collection)。也会触发STW
微信图片_20200306211206.png
新生代回收就会把存活的对象以复制的算法拷贝到幸存区。
微信图片_20200306211406.png
在工作一段时间,幸存区的对象也比较多了,有的对象寿命超过一定阈值,又会触发一次新生代垃圾回收。将寿命长的对象晋升到老年代。

2) Young Collection + CM

接下来就到第二阶段:新生代回收+并发标记阶段(Concurrent Marking)

  • 在Young GC新生代垃圾回收时,会先对GC Root进行初始标记,不会占用并发标记的时间。初始标记仅仅只是标记一下GC Roots能直接关联到的对象。
  • 老年代占用堆比例达到一定阈值时,会进行并发标记(不会STW)。阈值由下面的JVM参数决定:

-XX:InitiatingHeapOccupancyPercent=percent (默认是45%)

3) Mixed Collection

G1垃圾回收阶段的第三个阶段:混合收集
该阶段会回收所有新生代的region和部分老年代的Region
QQ图片20200307094722.png
在图中可以看出,还有的老年代经过并发标记后,幸存的对象也会用复制算法复制到新的老年代去。
而且G1收集器会有选择地对老年代进行回收。他会优先回收那些回收价值较高的老年代对象。回收这些对象释放的内存空间比较多。