一、如何确定垃圾

1、引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单
的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关
联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收
对象。
2、 可达性分析
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”
对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要 注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记
过程。两次标记后仍然是可回收对象,则将面临回收。

GC Roots 是指:
Java 虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈中引用的对象
方法区中常量引用的对象
方法区中类静态属性引用的对象
GC Roots 并不包括堆中对象所引用的对象,这样就不会有循环引用的问题。

二、垃圾回收算法

1.Mark-Sweep(标记-清除)算法  
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:
图片
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。  

2.Copying(复制)算法  
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:
图片
这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。  
很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
  
3.Mark-Compact(标记-整理)算法  
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:
图片
    
4.Generational Collection(分代收集)算法  
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。  
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。  
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。  
注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

三.典型的垃圾收集器
  垃圾收集算法是 内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。
1.Serial/Serial Old

Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,
必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,
Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。
它的优点是实现简单高效,但是缺点是会给用户带来停顿。

  2.ParNew

ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

  3.Parallel Scavenge

Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),
它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,
该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

  4.Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),
使用多线程和Mark-Compact算法。

  5.CMS

CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
它是一种老年代的并发收集器,采用的是Mark-Sweep算法。

  6.G1

G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器
它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

总结:

在新生代工作的收集器:Serial、ParNew、Parallel Scavenge
在老年代工作的收集器:CMS、Serial old、 Parallel old
同时工作在新生代和老生代的实用的收集器目前只有G1收集器

jdk1.7,jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)。
自JDK9以后G1收集器是默认收集器。
ZGC是一款jdk11版本支持下的垃圾回收器,支持TB级别的内存,
12306抢票软件一般都是TB级别的内存,STW时间不超过1ms, ZGC没有分代的概念。

image.png
新生代的收集器和老年代的收集器,是要配合使用的,而且配合选择上是有一定要求的。如上图中,新生代和老年代收集器能用钱连接在一起的,就是可以配合使用的。此外可以使用Serial old给CMS做备用收集器,当CMS不能工作的时候,切换到Serial old处理。


高旭
40 声望3 粉丝

« 上一篇
JVM
下一篇 »
JAVA 集合