08_其他因素

对象终止、软引用、弱引用、虚引用

应用程序有时会使用 finalization 和弱引用、软引用或虚引用与垃圾收集进行交互。

然而,不鼓励使用 finalization。它可能会导致安全、性能和可靠性问题。例如,依赖 finalization 来关闭文件描述符会使外部资源(描述符)依赖于垃圾收集的及时性,这可能会带来问题。

从JDK 9开始,finalization已经被废弃。而且据JEP 421:Deprecate Finalization for Removal规划,在JDK 18中将会被删除。

对象终止-Finalization

一个类可以声明一个 finalizer - 方法 protected void finalize() - 其方法体释放任何潜在资源。当对象不可达时,GC会安排不可达对象的 finalizer,在GC回收对象内存之前调用它。

当从GC根到对象没有路径时,对象变得不可达,因此有资格进行垃圾收集。GC根包括来自活动线程和内部JVM引用的引用;它们是保持对象在内存中的引用。

对象终止方法迁移

可以使用下面两种技术替代

The try-with-Resources Statement

Try-with-resources 语句是声明一个或多个资源的 try 语句。资源是程序结束后必须关闭的对象。Try-with-resources 语句确保在代码块结束时关闭每个资源,即使出现一个或多个异常。

The Cleaner API

如果您预见到应用程序中某个资源的生命周期将超出try-with-resources语句的范围,那么可以使用Cleaner API。Cleaner API 允许程序为对象注册一个清理操作,该操作在对象变得不可达后的一段时间内运行。

与 finalizer 相比,Cleaner 具有以下优点:

  • 更安全:必须显式注册清理程序,并且清理操作无法访问对象本身,因此无法实现对象复活。
  • 性能更好:您可以更多地控制何时注册清理操作,这意味着清理操作永远不会处理未初始化或部分初始化的对象。您还可以取消对象的清理操作。
  • 更可靠:可以控制哪些线程运行清理操作。

但是与 finalizer 一样,垃圾收集器安排了清理操作,因此它们可能会遭受无限延迟。因此,在需要及时释放资源的情况下,请不要在那些情况下使用 Cleaner API。

以下是一个简单的 Cleaner 示例。它执行以下操作:

  • 定义了一个清理操作类 State,该类初始化了清理操作并定义了自身的清理动作(通过重写 State::run() 方法)。
  • 创建了 Cleaner 的实例。
  • 使用 Cleaner 实例,为 myObject1 对象注册了一个 cleaning action(State 的一个实例)。
  • 为确保垃圾收集器安排了 Cleaner 和执行了 cleaning action State::run(),示例中进行如下操作:
  • 将 myObject1 设置为 null 以确保其成为虚不可达对象。
  • 循环调用 System.gc() 来触发垃圾回收进行清理。

图10-1 cleaner api 示例

import java.lang.ref.Cleaner;

public class CleanerExample {
    
    // This Cleaner is shared by all CleanerExample instances
    private static final Cleaner CLEANER = Cleaner.create();
    private final State state;

    public CleanerExample(String id) {
        state = new State(id);
        CLEANER.register(this, state);
    }

    // Cleaning action class for CleanerExample
    private static class State implements Runnable {
        final private String id;

        private State(String id) {
            this.id = id;
            System.out.println("Created cleaning action for " + this.id);
        }

        @Override
        public void run() {
            System.out.println("Cleaner garbage collected " + this.id);
        }
    }

    public static void main(String[] args) {
        CleanerExample myObject1 = new CleanerExample("myObject1");

        // Make myObject1 unreachable
        myObject1 = null;

        System.out.println("-- Give the GC a chance to schedule the Cleaner --");
        for (int i = 0; i < 100; i++) {
            
            // Calling System.gc() in a loop is usually sufficient to trigger
            // cleanup in a small program like this.
            System.gc();
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {}
        }
        System.out.println("-- Finished --");
    }
}

输出

Created cleaning action for myObject1

-- Give the GC a chance to schedule the Cleaner --

Cleaner garbage collected myObject1

-- Finished --

如果您要在生产环境中实现 Cleaner,请考虑以下事项:

  • 清理操作类(本示例中的 State)应作为私有实现细节。特别是,不应该从 main(String[]) 方法中使用它。因此,在实际可行的情况下,您的清理操作类应该是不可变的。一个新对象应该在其构造函数内处理创建自己的清理操作类,并将自身注册到 Cleaner 中。
  • 通常,类需要访问清理操作类内部的对象。最简单的方法是让对象保存对清理操作类的引用。
  • Cleaner 实例应该是共享的。在本示例中,所有 CleanerExample 的实例都应共享一个静态 Cleaner 实例。

引用-对象类型

有三种引用对象类型:SoftReference、WeakReference 和 PhantomReference。每种引用对象类型对应不同级别的可达性。以下是从最强到最弱的不同可达性级别,反映了对象的生命周期:

  • 如果某个线程可以在不遍历任何引用对象的情况下访问对象,则该对象是强可达的。新创建的对象由创建它的线程强可达。
  • 如果对象不是强可达,但可以通过遍历软引用来访问,则称为软可达。
  • 如果一个对象既不是强可达也不是软可达,但可以通过遍历弱引用来访问,则称为弱可达。当指向弱可达对象的弱引用被清除时,该对象就有资格进行最终化处理。
  • 如果一个对象既不是强可达、软可达、也不是弱可达,并且已经被最终化,某些虚引用指向它,则称为虚可达。
  • 如果一个对象以前以任何方式都无法被访问到,那么它就变得不可到达,因此有资格进行回收。

每种引用对象类型都封装了对特定对象的单个引用,这称为 referent(被引用物)。引用对象提供了清除 referent 的方法。

以下是引用对象实例的最常见应用:

  • 在允许系统释放内存(例如,可以根据需要重新生成的缓存值)时,保持对一个对象的访问
  • 确定并可能在某个特定可达性级别下采取一些操作(与 ReferenceQueue 类结合使用)

显式垃圾收集

另一种应用程序与垃圾收集进行交互的方式是通过显式调用 System.gc() 来执行完整的垃圾收集。

这可能会在不必要的情况下强制进行大规模的垃圾收集(例如,当进行小规模垃圾收集就足够时),因此通常应避免使用。显式垃圾回收的性能影响可以通过使用标志 -XX:+DisableExplicitGC 来禁用它进行测量,该标志使 VM 忽略对 System.gc() 的调用。

显式垃圾回收最常见的应用之一是使用远程方法调用(RMI)的分布式垃圾回收(DGC)。使用 RMI 的应用程序引用其他虚拟机中的对象。在这些分布式应用程序中,如果不间或地调用本地堆的垃圾回收,就无法对垃圾进行回收,因此 RMI 会定期强制进行完整的垃圾收集。可以通过属性来控制这些收集的频率,以下是一个例子:

java -Dsun.rmi.dgc.client.gcInterval=3600000
    -Dsun.rmi.dgc.server.gcInterval=3600000 ...

这个例子指定了每小时执行一次显式垃圾回收,而不是默认的每分钟一次。然而,这可能也会导致一些对象需要更长的时间才能被回收。如果不希望对 DGC 活动的及时性设置上限,这些属性可以设置为 Long.MAX_VALUE,使得显式收集之间的时间在实际上变为无限。

软引用

在服务器虚拟机中,软引用比客户端虚拟机中的存活时间长。

通过命令行选项 -XX:SoftRefLRUPolicyMSPerMB=<N> 可以控制清除速率,该选项指定每兆字节堆空闲空间中软引用将保持存活的毫秒数(ms) 。默认值为每兆字节1000毫秒,这意味着一个软引用在最后一个强引用对象被回收后,将会在堆空间中的每兆字节空闲空间中存活1秒。这是一个近似值,因为软引用只有在垃圾收集期间才会被清除,并且垃圾收集可能偶发发生。

类元数据

Java类在Java Hotspot VM中具有内部表示,被称为类元数据。

在之前的Java Hotspot VM版本中,类元数据被分配在所谓的永久代中。从JDK 8开始,永久代被移除,类元数据被分配在本机内存中。用于类元数据的本机内存的默认使用量是无限的。可以使用选项 -XX:MaxMetaspaceSize 来设置用于类元数据的本机内存的上限。

Java Hotspot VM明确管理用于元数据的空间。空间从操作系统请求然后分成块。一个类加载器从其块(一个块绑定到特定的类加载器)中为元数据分配空间。当类被卸载时,其块将被回收以供重用或返回给操作系统。元数据使用由 mmap 分配的空间,而非 malloc。

如果启用了 -XX:UseCompressedOops 选项并且使用了 -XX:UseCompressedClassesPointers 选项,则逻辑上会使用两个不同区域的本机内存来存储类元数据。-XX:UseCompressedClassPointers 使用32位偏移量来表示64位进程中的类指针,就像 -XX:UseCompressedOops 用于Java对象引用一样。为这些压缩过的类指针(32位偏移量)分配了一个区域。区域大小可以使用 -XX:CompressedClassSpaceSize 设置,默认为1GB。压缩类指针的空间作为初始化时由 -XX:mmap 分配并根据需要提交。-XX:MaxMetaspaceSize 应用于已提交压缩类空间和其他类元数据空间之和。

当相应的Java类被卸载时,会释放对应的类元数据。由于垃圾回收而导致Java 类卸载,并且可能会因此卸载并释放对应的元数据。当为类元数据提交的空间达到一定水平(高水位标记)时,将触发垃圾回收。在垃圾收集之后,根据从类元数据释放出来的空间量高水位标记可能会升高或降低。高水位标记将被提高以避免过早地再次触发垃圾回收。高水位标记最初设置为命令行选项 -XX:MetaspaceSize 的值,并基于选项 -XX:MaxMetaspaceFreeRatio 和 -XX:MinMetaspaceFreeRatio 进行调整。如果作为已提交空间比总已提交空间百分比更大,则高水位标记将得到降低;如果小于 -XX:MinMetaspaceFreeRatio,则高水位标记将得到提升。

为选项-XX:MetaspaceSize 指定更高值以避免由于太早触发垃圾回收而导致对于应用程序进行了很多处理度去处理所使用开销让程序产生累计较大开销对应关系表记录与传输关系表记录累计过大LED显示屏良好L描述状语资料去v立靶向行业企业公司差异化供给模式取得联合竞争优势产生关联共同研发 Lightspeed Venture最快速启速资金帮助企业赢得数量投资额丛海外方投8万港币共购退智能订单系统款取步俱进落细节公司电商经济模式升净利润润奈555万21万元21亿详情IN 化解激烈竟争共赢市场份额蜘蛛人可见化看"},"body":"英俊国华覅哎我求似懂非懂

如有需要我们可以继续交流

[0,296s][info][gc,heap,exit] Heap
[0,296s][info][gc,heap,exit] garbage-first heap total 514048K, used 0K [0x00000005ca600000, 0x00000005ca8007d8, 0x00000007c0000000)
[0,296s][info][gc,heap,exit] region size 2048K, 1 young (2048K), 0 survivors (0K)
[0,296s][info][gc,heap,exit] Metaspace used 2575K, capacity 4480K, committed 4480K, reserved 1056768K
[0,296s][info][gc,heap,exit] class space used 238K, capacity 384K, committed 384K, reserved 1048576K

在以 Metaspace 开头的那行中,used 表示已用于加载类的空间;capacity 表示当前分配的块中用于元数据的可用空间;committed 表示块中可用空间的数量;reserved 表示为元数据保留的(但不一定已提交)空间量。以 class space 开头的那行包含了压缩类指针元数据对应的相应数值。

相关推荐
顶呱呱程序5 分钟前
2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能
算法·matlab·音视频·matlab-gui·音频滤波·脉冲响应不变法
StayInLove16 分钟前
G1垃圾回收器日志详解
java·开发语言
对许20 分钟前
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“
java·log4j
无尽的大道23 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒27 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~31 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
小鑫记得努力33 分钟前
Java类和对象(下篇)
java
binishuaio36 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE38 分钟前
【Java SE】StringBuffer
java·开发语言
老友@38 分钟前
aspose如何获取PPT放映页“切换”的“持续时间”值
java·powerpoint·aspose