1 ZGC收集器
ZGC(Z Garbage Collector)是一个可扩展的、低延迟的收集器。ZGC可以并发地执行所有系统资源消耗高的GC工作负载,在GC阶段只需要几毫秒的GC暂停时间。因此,ZGC收集器适用于需要低延迟的应用。
GC暂停时间不依赖于JVM的堆区空间大小,其支持的堆空间大小范围是从8MB到16TB。
目前,ZGC收集器不是JVM的默认收集器,用户可以使用命令行参数-XX:+UseZGC启用ZGC收集器。
1.1 设置堆空间大小
在ZGC的所有优化调整参数中,最重要的参数是设置最大堆空间大小的命令行参数-Xmx。
由于ZGC是并发收集器,所以,其最大堆空间设置的依据包括以下两点:
-
堆区空间能容纳应用所有的存活对象
-
具有足够的剩余堆区空间,提供给运行中的应用为新建对象申请堆区空间
需要保留多大的剩余堆区空间,这依赖于应用申请堆区空间的比率以及应用内所有存活对象占用的总堆区空间大小。
总而言之,分配给ZGC内存空间越多越好。但是,为了不浪费内存资源,需要在实际使用所需的内存空间以及实际运行所需的内存空间两者之间找到一种平衡的设置。
1.2 设置并发GC线程数
用户可以使用命令行参数-XX:ConcGCThreads设置并发GC操作的线程数。默认地,ZGC是使用启发式的行为分析方法设置该值,启发式所设置的值可以满足大部分的应用需求,该设置值本质上是指示分配给GC的处理器资源,ZGC分配越多,则处理应用业务的处理器资源越少。ZGC分配得越少,则处理应用业务的处理器资源越多,ZGC分配处理器资源太少会引起GC收集操作速度太慢,从而会引起很多非存活对象无法及时回收。
1.3 返还未使用内存空间
默认地,ZGC会恢复未使用的内存空间,并返还给操作系统。该功能适用于一些内存计算型应用以及环境。用户也可以使用命令行参数-XX:-Zuncommit关闭此功能,如果关闭此功能,则ZGC会自动缩放堆空间的大小。当-Xms设置值等于-Xmx设置值,该功能被默认关闭。
用户也可以使用命令行参数-XX:ZUncommitDelay=<seconds>设置延迟恢复未使用内存空间的时间(默认值是300秒)。也就是当内存空间变成未使用状态时,ZGC需要再等待多长的时间才可以恢复该内存空间并返回给操作系统。
2 引用对象收集
本章节主要从代码层面描述GC收集。其主要包括Java对象提供的finalization方法以及有关Java对象的弱引用、软引用、虚引用。
2.1 Finalization方法
一般情况下,不推荐使用Java Object提供的finalization方法,使用该方法容易引起安全问题、性能问题或者可靠性问题。因为该方法是发生GC操作的情况下才执行。
Java对象提供的抽象方法是protected void finalize(),用户可以实现该方法,在该方法中释放对应的资源,例如文件句柄。JVM执行GC操作的时候,在释放非存活对象堆空间之前调用该方法。
Java堆区中的一个存活对象变成非存活时,也就是,收集器从JVM线程的根节点以及从JVM内部引用开始扫描该对象的引用关系,发现该对象不可达时,即标识该对象成为非存活对象。然后,JVM的收集器将回收该非存活对象的堆空间。
用户可以使用jcmd <pid> GC.finalizer_info命令行参数查看JVM对该方法的执行情况。其中pid是当前JVM的进程ID。
2.2 try-with-Resources方法
从代码优化的层面考虑,JDK提供其他方法可以替代Java对象提供的Finalization的方法。try-with-resources方法可以自动关闭代码块中的资源。
2.3 Cleaner API方法
从代码优化的层面考虑,如果在try-with-resources代码块范围之外替代Finalization方法,可以使用Cleaner API方法。该方法提供的方式是在每个Java对象内注册一个Cleaner,当该Java对象变成非存活对象的时候,JVM会在发生GC操作时回收该Java对象,并且调用该Java对象注册的Cleaner的处理方法。
使用Cleaner API方法的好处是:
-
更加安全,每个Java对象注册一个Cleaner,Cleaner不能访问Java对象
-
更好性能,可以独立于Java对象,更容易控制
-
更加可靠,可以控制那个线程执行Cleaner
但是,Cleaner API也有缺点,需要等待JVM收集器的调度,所以其不适用于需要及时回收资源的使用场景。
Cleaner API使用实例如下所示:
以上的代码实例输出如下所示:
此外,使用Cleaner API方法需要注意以下几点:
-
实现的State类必须是私有类型,不能在Java对象的main中使用,因此,在实际使用环境中State类需要保持不可变。每个Java对象需要单独绑定一个Cleaner,并且在Java对象的构造方法中实行初始化。
-
Java对象如果需要访问Cleaner内的引用,可以在Java对象内建立对Cleaner的引用
-
在Java对象的内部,可以定义静态类型的Cleaner,同一个Java类的多个实例可以共享同一个Cleaner实例
2.4 引用对象类型
Java对象的引用类型包括三种引用类型:软引用(SoftReference)、弱引用(WeakReference)以及虚引用(PhantomReference)。
由前面章节的分析可知,JVM的收集器判断一个Java对象是否是存活对象或者是非存活对象的依据是Java对象的可达性。因此,以上三种引用类型是根据Java对象不同级别的可达性予以区分,也就是,可达性的级别是从最强引用到最弱引用的层次级别划分,该级别也标识着对象引用的存活周期。从以下几个方面描述:
-
强可达对象,该对象可以直接从线程中可达,不必要通过以上三种对象引用类型才可达,也就是一个新创建的对象是强引用对象,通过创建该对象的线程可达
-
软可达对象,该对象是非强可达对象,可以通过一层软引用(SoftReference)可达
-
弱可达对象,该对象既不是强可达对象,也不是软可达对象,可以通过一层弱引用(WeakReference)可达,也就是,当弱引用被回收,则被弱引用对象引用的弱可达对象状态变为可被finalization方法执行的状态
-
虚可达对象,该对象是非强可达对象、非软可达对象、非弱可达对象,该对象已经被执行对象的finalize方法,可以通过一层弱引用(WeakReference)可达
-
不可达对象,该对象是非强可达对象、非软可达对象、非弱可达对象、非虚可达对象,该对象在等待JVM收集器的回收
由上面的分析可知,以上三种对象引用类型都封装对某个对象的单一引用,该被引用的对象被称之为引用。以上三种对象引用类型提供方法清除被引用的对象。
以上三种对象引用类型的适用场景如下:
-
作为系统的缓存,缓存具有失效时限,该对象在被JVM收集器回收之前,都可以作为缓存使用
-
作为ReferenceQueue类使用,当引用对象到达一种引用可达级别的时候,执行某些动作
2.5 显式调用GC
在代码层面,用户可以使用System.gc()代码直接触发Full GC收集操作,用户也可以使用-XX:+DisableExplicitGC命令行参数关闭调用System.gc()时触发Full GC收集操作。
2.6 关于软引用
用户可以使用-XX:SoftRefLRUPolicyMSPerMB=<N>命令行参数设置软引用对象的存活时间,也就是,一旦当软引用对象变成非可达的时候,软引用对象的存活时间取决于空闲堆区的大小,平均每1MB空闲堆区对应1000毫秒的存活时间,这只是一个近似的计算,因为软引用对象的收集是在GC操作阶段发生时才执行。
2.7 关于类的元数据
Java类的元数据是Java类在JVM内的表示形式。
在JDK8之前,Java类的元数据信息是保存在永久代堆区空间中,但是在JDK8之后,Java类的元数据信息是直接保存在操作系统的本地内存中,默认是无限制的,但是用户可以使用命令行参数-XX:MaxMetaspaceSize设置Java类元数据的内存空间的最大值。
JVM直接从操作系统中申请内存空间,用于保存Java类的元数据信息,该内存区域被分为多个内存块。JVM为每个类加载器从这些内存块空间中分配内存块,用于保存Java类的元数据信息,当类从该类加载器中卸载,则JVM重用该内存块或者返回给操作系统,该功能使用的操作系统的函数是mmap,而不是malloc。
(未完待续)