JVM专题八:JVM如何判断可回收对象

news2024/11/15 20:51:56

在JVM专题七:JVM垃圾回收机制中提到JVM的垃圾回收机制是一个自动化的后台进程,它通过周期性地检查和回收不可达的对象(垃圾),帮助管理内存资源,确保应用程序的高效运行。今天就让我们来看看JVM到底是怎么定义“垃圾”对象的。

JVM垃圾对象判断方法

JVM在垃圾回收(Garbage Collection简称GC)过程中定义“垃圾”对象,主要是根据对象是否还可达来判断。以下是一些判断对象是否成为垃圾的常见标准:

  1. 引用计数法:这是一种简单的垃圾回收方法,每个对象都有一个引用计数器,每当有引用指向该对象时,计数器加一;每当引用离开作用域或被赋值为null时,计数器减一。当引用计数器为零时,对象被认为是垃圾。但这种方法无法处理循环引用的问题。

  2. 可达性分析:这是现代JVM中常用的垃圾回收方法。从一系列的“GC Roots”开始,所有能够通过引用链到达的对象都被认为是存活的,而那些无法到达的对象则被认为是垃圾。GC Roots通常包括:

    • 虚拟机栈(栈帧中的本地变量表)中的引用对象
    • 方法区中的类静态属性引用的对象
    • 运行时常量池中引用的对象
    • JNI(Java Native Interface)的引用对象
  3. 不可达的对象:即使对象不可达,JVM也不会立即回收它们。JVM会将这些对象标记为“即将回收”的状态。在下一次垃圾回收周期中,这些对象会被真正回收。

  4. 对象的finalize()方法:如果对象在被标记为垃圾之前定义了finalize()方法,JVM会给予对象最后一次“自救”的机会。在下一次垃圾回收时,JVM会尝试调用这个对象的finalize()方法。如果对象在finalize()方法中重新与GC Roots建立了连接,它将被移出即将回收的列表。

  5. 分代收集:JVM通常采用分代收集算法,将对象分为新生代和老年代。新生代中的对象通常存活时间较短,而老年代中的对象存活时间较长。JVM会根据对象的年龄来决定它们应该在哪个区域。

垃圾回收是一个复杂的过程,JVM会根据当前的内存使用情况和对象的生命周期来决定何时以及如何回收对象。

JVM回收对象示例

局部变量作为GC Roots

public class App {
    public static void main(String[] args) {
        runApp(args);
        System.out.println("====");

    }

    private static void runApp(String[] args) {
        SpringApplication sApp = new SpringApplication();
        sApp.run(App.class, args);
//        sApp.getRunListeners(args);

    }
}



public class SpringApplication {

    public String run(Class appClass, String[] args) {
        System.out.println("my Spring Application ");
        return "run args";
    }

    public String getRunListeners( String[] args) {
        System.out.println("My  getRunListeners ");
        String myRunListenersVar = "myRunListenersVar";
        this.getSpringFactoriesInstances(args);
        return "run args";
    }

    public String getSpringFactoriesInstances( String[] args) {
        System.out.println("My  getSpringFactoriesInstances ");
        String mySpringFactoriesInstancesVar = "mySpringFactoriesInstances";

        return "run args";
    }


}

上述代码中,最关键的一段是 SpringApplication sApp = new SpringApplication();代码分析如下图所示:

JVM垃圾回收线程扫描到Spring Appliedcation发现它有一个GC Roots,此时就不能回收了。

静态变量作为GC Roots

除了上述代码外,咱们在看一个常见的代码。

public class App {
    public static SpringApplication sApp = new SpringApplication();

    public static void main(String[] args) {
        runApp(args);
        System.out.println("====");

    }

    private static void runApp(String[] args) {
        SpringApplication sApp = new SpringApplication();
        sApp.run(App.class, args);

    }
}

上述 public static SpringApplication sApp = new SpringApplication() 代码分析如下图所示:

JVM垃圾回收线程扫描到Spring Appliedcation发现它有一个GC Roots,此时就不能回收了。相信有细心的小伙伴注意到这块代码与上述代码在示意图上的区别了,他们GC Roots 所在区域不一样,一个是在JVM栈中,一个是在JVM的方法区且特意把颜色换成同堆内存一样的颜色,其实就是想说这块区域是共享内存,而局部变量是线程内存。

因此这里留一个小小的思考问题:大家直观感受下这两个引用谁引用持有的时间会更长一点?

简单总结,只要对象被局部变量或静态变量引用就不会被回收。

Java中不同对象类型

我们在进行可达性分析的时候常常提到局部变量持有某个对象的引用,或者静态变量持有某个对象引用,但是我们脑海里需要清楚一个概率那就是java中有不同的引用类型,他们分别是强引用、软引用、弱引用和虚引用。

强引用

强引用是最常见的引用类型,它使得对象在任何情况下都不会被垃圾回收,除非显式地将引用设置为null或超出作用域。如下代码所示:

public class App {
    public static SpringApplication app = new SpringApplication();
}

软引用

软引用提供了一种在内存不足时可以被回收的机制。它们通常用于缓存,当内存足够时,对象不会被回收,但如果内存不足,垃圾回收器会尝试回收这些对象。使用软引用可以提高应用的内存效率,但需要权衡对象被回收的概率.

import java.lang.ref.SoftReference;
// ...

public class ConfigurationPropertyUtils {

    // ...

    private static final SoftReference<ConfigurationPropertyCache> cache = 
        new SoftReference<>(null);

    public static <T> T getCached(BeanFactory beanFactory, Class<T> cacheType) {
        // ...
        SoftReference<ConfigurationPropertyCache> ref = cache;
        ConfigurationPropertyCache cpCache = (ref != null ? ref.get() : null);
        if (cpCache == null) {
            cpCache = new ConfigurationPropertyCache(beanFactory, cacheType);
            cache = new SoftReference<>(cpCache);
        }
        // ...
        return cpCache.getProperty();
    }

    // ...
}

在这段代码中,ConfigurationPropertyUtils类使用了一个静态的软引用cache来存储配置属性的缓存。当调用getCached方法时,如果缓存不存在或已被垃圾回收,就会创建一个新的ConfigurationPropertyCache实例,并用软引用包装后存储起来。由于软引用对象在内存不足时会被回收,这有助于减少垃圾回收器的压力,避免长时间的GC停顿,提高应用的响应性。

所以在软引用放到缓存这个场景就非常实用,想想内存充足我就不回收,内存不够了我回收而缓存基本上都是为了提升性能的,现在内存都不足了还有啥自行车,能运行就不错啦 !

弱引用

弱引用不会阻止垃圾回收器回收对象,即使内存充足也会被回收。它们通常用于监听或观察对象的状态,而不阻止对象的回收。弱引用在内存不足时更有可能被回收,因为它们对对象的生命周期没有任何控制。

public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
		implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {

	private final Object poolSizeMonitor = new Object();

	private int corePoolSize = 1;

	private int maxPoolSize = Integer.MAX_VALUE;

	private int keepAliveSeconds = 60;

	private int queueCapacity = Integer.MAX_VALUE;

	private boolean allowCoreThreadTimeOut = false;

	@Nullable
	private TaskDecorator taskDecorator;

	@Nullable
	private ThreadPoolExecutor threadPoolExecutor;

	// Runnable decorator to user-level FutureTask, if different
// 聚焦这里哈
	private final Map<Runnable, Object> decoratedTaskMap =
			new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);

}

上述代码是Spring的ThreadPoolTaskExecutor节选部分,注意ThreadPoolTaskExecutor本身并不直接使用弱引用来引用任务对象。它主要使用弱引用来缓存线程池中的Future任务,以便在内存不足时可以回收这些不再需要的Future对象。这样做可以避免因缓存大量已完成或取消的任务的Future引用而导致的内存泄漏问题。

        一个对象被线程池持有强引用,那么即使这个对象不再被其他地方使用,它也不会被垃圾回收器回收,因为线程池中的引用阻止了垃圾回收。使用弱引用可以确保当没有其他强引用存在时,线程池中的任务可以被垃圾回收。

虚引用

虚引用是最弱的引用类型,它们几乎不提供任何保护,对象可以随时被回收。虚引用的主要作用是跟踪对象被回收的状态,通过ReferenceQueue来接收通知。

因为虚引用接触比较少,暂时就不讨论了,下面对他们四个应用简单总结下:

  • 强引用:只要强引用存在,对象就不会被垃圾回收。
  • 软引用:内存不足时,软引用对象会被垃圾回收,适用于缓存等场景。
  • 弱引用:弱引用对象在下一次垃圾回收时会被回收,适合用于监听或观察对象状态。
  • 虚引用:虚引用几乎不阻止对象回收,主要用于跟踪对象被垃圾回收的状态

到这里,我们已经Java介绍完垃圾对象的判断方法,并举出例子演示给大家看;同样提出几个问题:我们在介绍完GC Roots的时候分为不同类型的时候提出问题,他们引用时间的长短对于引用时间长短不同可以等价不同的对象实例存活时间。不同对象回收的机制一样嘛?如果不一样根据什么特点区分呢?回收以后内存空间要怎么整理呢?本章结束:有时间小伙伴可以回头看看,前面的文章回忆下各个区域的特点和整个分配的过程。从下一章我们将开始慢慢介绍GC算法,需要前面的铺垫。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1857983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Shopee API接口:获取搜索栏生成的商品结果列表

一、引言 此接口可以高效获取搜索栏生成的商品结果列表。本文将详细介绍这一核心功能&#xff0c;并探讨其在实际应用中的价值。 二、核心功能介绍——获取搜索栏生成的商品结果列表 请求API及返回示例 http://api.xxxx.com/sp/ll/search/item?keywordiphone&page1&am…

零门槛用AI,302.AI让人工智能变得简单易用

当下人工智能火爆&#xff0c;提到AI&#xff0c;几乎每个人都能说上几句&#xff0c;但是你真的会使用AI吗&#xff1f; 当涉及到如何实际使用AI时&#xff0c;许多人可能会觉得它太过高深莫测&#xff0c;从而产生一种距离感&#xff0c;不知如何开始。我和大家也一样&#x…

期末考试的成绩怎么发?

随着学期末的临近&#xff0c;我们又迎来了向家长通报学生成绩的关键时刻。下面是一份成绩群发的全新指南&#xff0c;让我们一起高效而温馨地完成这项任务&#xff01; 1.选择沟通渠道&#xff1a; - 邮件与短信各有优势。邮件更适合提供详尽的成绩分析和评语&#xff0c;而短…

云计算【第一阶段(18)】磁盘管理与文件系统 分区格式挂载(一)

目录 一、磁盘基础 二、磁盘结构 2.1、机械硬盘 2.2、固态硬盘 2.3、扩展移动硬盘 2.4、机械磁盘的一些计算&#xff08;了解&#xff09; 2.5、磁盘接口类型 二、Linux 中使用的文件系统类型 2.1、磁盘分区的表示 2.1.1、主引导记录(MBR) 2.1.2、Linux中将硬盘、分…

【UIDynamic-动力学-UIPushBehavior-推行为 Objective-C语言】

一、接下来,我们来说这个,推行为, 1.推行为,首先,它叫做UIPushBehavior, 这个里边呢,又分为持续推力、瞬时推力, 我们新建一个项目,叫做:13-推行为 我们这个里边,还是先来一个redView, UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(100,100,…

二刷算法训练营Day41 (Day40休息) | 动态规划(3/17)

目录 详细布置&#xff1a; 1. 背包问题理论基础 1.1 01背包 2. 46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; 一维dp数组&#xff08;滚动数组&#xff09; 3. 416. 分割等和子集 详细布置&#xff1a; 1. 背包问题理论基础 但说实话&#xff0c;背包九讲…

ONLYOFFICE 8.1全新升级,智能办公体验再升级,引领未来工作新潮流!

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ONLYOFFICE 8.1 &#x1f4d2;1. ONLYOFFICE简介&#x1f4d9;2. ONLYOFFICE特点&#x1f4d5;3. ONLYOFFICE功能⛰️PDF 文件编辑器&#x1…

win10系统管理员账号怎么切换

1、按住“windowsx”&#xff0c;选择“计算机管理” 2、在页面左侧&#xff0c;找到“计算机管理(本地)”&#xff0c;展开“系统工具”&#xff0c;点击“本地用户和组”下面的“用户”&#xff0c;在右侧找到“Administrator”&#xff0c;双击打开。 3、在打开页面选择常规…

【分布式事务】Seata AT实战

目录 Seata 介绍 Seata 术语 Seata AT 模式 介绍 实战&#xff08;nacos注册中心&#xff0c;db存储&#xff09; 部署 Seata 实现 RM 实现 TM 可能遇到的问题 1. Seata 部署成功&#xff0c;服务启动成功&#xff0c;全局事务不生效 2. 服务启动报错 can not get …

Windows安装jdk配置环境变量(基础)

一、下载安装JDK 下载地址:https://www.oracle.com/java/technologies/downloads/?er=221886#java8-windows 因为JDK8比较稳定,所以建议选择这个。电脑32位的下载jdk-8u411-windows-i586.exe;电脑是64位的下载jdk-8u411-windows-x64.exe 1、根据自己电脑的配置下载相应的…

C++使用Poco库封装一个FTP客户端类

0x00 Poco库中 Poco::Net::FTPClientSession Poco库中FTP客户端类是 Poco::Net::FTPClientSession , 该类的接口比较简单。 上传文件接口&#xff1a; beginUpload() , endUpload() 下载文件接口&#xff1a; beginDownload() , endDownload() 0x01 FTPCli类说明 FTPCli类…

Docker(六)-本地镜像发布到私有库

1.下载镜像Docker Registry 用于搭建私人版本Docker Hub docker pull registry2.运行私有库Registry 运行私有库Registry&#xff0c;相当于本地有个私有Docker hubdocker run -d -p hostPort:containerPort -v 【宿主机目录】:【容器目录】 --privilegedtrue 【私有库镜像】…

群晖NAS部署VoceChat私人聊天系统并一键发布公网分享好友访问

文章目录 前言1. 拉取Vocechat2. 运行Vocechat3. 本地局域网访问4. 群晖安装Cpolar5. 配置公网地址6. 公网访问小结 7. 固定公网地址 前言 本文主要介绍如何在本地群晖NAS搭建一个自己的聊天服务Vocechat&#xff0c;并结合内网穿透工具实现使用任意浏览器远程访问进行智能聊天…

PS添加物体阴影

一、选择背景&#xff0c;确保物体和北京分割出图层 二、右键单击物体图层&#xff0c;点击混合选项&#xff0c;点击投影 三、调整参数&#xff0c;可以看效果决定(距离是高度&#xff0c;扩展是浓度&#xff0c;大小是模糊程度)&#xff0c;保存即可

dp经典问题:LCS问题

dp&#xff1a;LCS问题 最长公共子序列&#xff08;Longest Common Subsequence, LCS&#xff09;问题 是寻找两个字符串中最长的子序列&#xff0c;使得这个子序列在两个字符串中出现的相对顺序保持一致&#xff0c;但不要求连续。 力扣原题链接 1.定义 给定两个字符串 S1…

Apple - Game Center Programming Guide

本文翻译整理自&#xff1a;Game Center Programming Guide&#xff08; Updated: 2016-06-13 https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/GameKit_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008304 文章…

什么是zip格式?zip格式文件要怎么打开,一文详解

Zip是一种常见的压缩文件格式&#xff0c;广泛应用于文件和文件夹的打包和压缩。它的使用方便、文件体积小&#xff0c;是网络传输和存储文件时的常用选择。本文将深入介绍Zip格式的定义、特点以及它在现代计算机应用中的重要性。 zip是什么文件&#xff1f; ZIP是一种相当简单…

专业竞赛组织平台赛氪网,引领大学生竞赛新时代

随着互联网技术的快速发展&#xff0c;高校学科竞赛组织和管理正迎来新的变革。环球赛乐&#xff08;北京&#xff09;科技有限公司&#xff08;以下简称”赛氪网“&#xff09;&#xff0c;作为一家专业竞赛组织平台不仅致力于大学生成长和前途的拓展&#xff0c;更在推动学科…

【Android】Android Studio 使用Kotlin写代码时代码提示残缺问题解决

问题描述 Android Studio升级之后&#xff0c;从Android Studio 4.2升级到Android Studio Arctic Fox版本&#xff0c;因为项目比较老&#xff0c;使用的Gradle 版本是3.1.3&#xff0c;这个版本的Android Studio最低支持Gradle 3.1版本&#xff0c;应该算是比较合适的版本。 …

【Redis】如何保证缓存和数据库的一致性

目录 背景问题思路 三个经典的缓存模式Cache-Aside读缓存写缓存为什么是删除旧缓存而不是更新旧缓存&#xff1f;为什么不先删除旧的缓存&#xff0c;然后再更新数据库&#xff1f; 延迟双删如何确保原子性 Read-Through/Write-ThroughRead-ThroughWrite-Through Write Behind …