Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 死亡对象判断方法

news2024/12/26 11:14:08

文章目录

    • 垃圾回收机制
    • 死亡对象判断方法
      • 引用计数法
      • 可达性分析算法
        • 可以作为 GC Roots 的对象
        • 判断对象被回收需要经历的过程
      • 引用类型
        • 引用汇总
        • 引用队列
      • 废弃常量
        • 判定废弃常量
        • 废弃原因
        • 遵循原则
      • 无用的类
        • 所需条件
        • 造成的问题
        • 解决步骤

垃圾回收机制

垃圾回收Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收

死亡对象判断方法

JVM(Java虚拟机)垃圾回收机制 —— 内存分配和回收规则

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)

引用计数法

给对象中添加一个引用计数器

  • 每当有一个地方引用它,计数器就加 1;
  • 当引用失效,计数器就减 1;
  • 任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

![[Pasted image 20231011222409.png]]

所谓对象之间的相互引用问题:除了对象 objAobjB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们

public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}

可达性分析算法

该算法通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

![[Pasted image 20231011222616.png]]

图片来源:JavaGuide

Object 6 ~ Object 10 之间虽有引用关系,但它们到 GC Roots 不可达,因此为需要被回收的对象

可以作为 GC Roots 的对象
  • 虚拟机栈(栈帧中的局部变量表)中引用的对象

  • 本地方法栈(Native 方法)中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 所有被同步锁持有的对象

  • JNI(Java Native Interface)引用的对象

判断对象被回收需要经历的过程

可达性算法是一种通过判断对象是否可达来确定是否回收的方法。对象被判定为不可达时,并非意味着它们会立即被回收,而是在垃圾回收器执行回收操作时才会被释放。具体的回收时机和策略由垃圾回收器的实现决定

可达性算法中,判断对象是否被回收通常经历以下过程:

  1. 根对象标记:垃圾回收器会从一组称为"根对象(GC Roots)"(如全局变量、活动线程的栈等)开始,标记所有从根对象可直接或间接访问到的对象。这些被标记的对象被认为是"可达"的,应该保留不被回收。

  2. 遍历标记:垃圾回收器会逐个遍历可达对象,标记它们所引用的其他对象。这个过程是递归的:在找到一个对象并标记它后,继续查找并标记被该对象引用的其他对象,直到不再有新对象可访问。

  3. 清理非标记对象:在标记阶段完成后,垃圾回收器会遍历堆内存中的所有对象。未被标记的对象被视为不可达,它们不再被任何可达对象引用或访问。垃圾回收器将这些非标记对象标记为垃圾,并将它们的内存释放出来。

  4. 内存回收:垃圾回收器会执行垃圾回收操作,将被标记为垃圾的对象所占用的内存空间进行回收释放。这个过程通常是自动的,由垃圾回收器负责管理和执行。

引用类型

无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。

JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用

JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用 四种(引用强度逐渐减弱)

引用汇总

1.强引用(Strong Reference)

以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品垃圾回收器绝不会回收它

当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

2.软引用(Soft Reference)

如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存

只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。

3.弱引用(Weak Reference)

如果一个对象只具有弱引用,那就类似于可有可无的生活用品

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue) 联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4.虚引用(Phantom Reference)

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收

虚引用主要用来跟踪对象被垃圾回收的活动

虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

特别注意,在程序设计中一般很少使用弱引用与虚引用

使用软引用的情况较多,软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生

引用队列

引用队列(Reference Queue) 是 Java 中用于管理对象引用的特殊队列。它与垃圾回收(Garbage Collection)机制密切相关,用于跟踪和处理被垃圾回收器回收的对象。

当对象被垃圾回收器标记为可回收时,它可能会被加入到引用队列中。通过监视引用队列,我们可以了解对象何时被回收,并进行相应的处理操作

当一个对象的引用类型存在于引用队列中时,我们可以使用 poll() 方法或 remove() 方法从引用队列中获取对应的引用对象。这样,我们就可以得知某个对象何时被垃圾回收器回收了,并进行一些相关的处理工作,比如资源释放、记录日志等。

引用队列在某些场景下非常有用,比如内存敏感的缓存系统、对象终结(Finalization)等。但是需要注意,使用引用队列需要小心处理,避免导致内存泄漏或引起性能问题。

废弃常量

运行时常量池主要回收的是废弃的常量

  • JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区,此时 hotspot 虚拟机对方法区的实现为永久代

  • JDK1.7 字符串常量池被从方法区拿到了堆中,这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区,也就是 hotspot 中的永久代 。

  • JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之,这时候字符串常量池还在堆,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间(Metaspace)

相关知识补充:Java 入门指南:JVM(Java虚拟机) —— Java 内存运行时的数据区域

废弃常量(Deprecated Constants)是指在编程语言或库的API中标记为过时或不推荐使用的常量。它们通常是为了提醒开发者不要再依赖或使用这些常量,并鼓励使用新的替代方案。

假如在字符串常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池了。

在Java中,使用@Deprecated注解可以将常量标记为废弃。

public class Constants { 
	@Deprecated 
	public static final int OLD_CONSTANT = 100; 
	public static final int NEW_CONSTANT = 200; 
	// ... 
	}
// 当其他代码中引用此废弃常量时,会收到编译器的警告,以提醒开发者不再使用该常量。
判定废弃常量

要判定一个常量是否被标记为废弃(Deprecated),可以通过以下步骤进行

  1. 查看文档或注释:首先,查看常量的文档或注释,看是否有明确的说明该常量已被废弃。通常,废弃的常量会在文档或注释中标记为废弃,并给出了推荐的替代方案。

  2. 阅读源代码:如果没有明确的文档或注释,可以查看常量所在类的源代码。在常量的定义处,通常使用 @Deprecated 注解将废弃常量标记为废弃。在 Java 中,使用了 @Deprecated 注解的常量会在编译时发出警告。

  3. IDE 提示:在使用现代集成开发环境(IDE)时,常量的废弃状态通常会得到特殊标记或提示。IDE 可能会在代码中给出警告,提示开发者一个常量已被废弃

如果经过以上步骤确认一个常量被标记为废弃,那么开发者应该遵循推荐的做法,并尽量避免使用该废弃常量。替代方案往往会在文档或注释中提到,开发者可以根据推荐的替代方案进行调整和更新代码。

废弃原因

标记常量为废弃的原因可以包括

  • 常量已不再符合设计或功能要求。
  • 常量存在安全风险或潜在问题,不推荐使用。
  • 常量已被更好的替代方案取代,推荐使用新的常量。
遵循原则

开发者在使用废弃常量时应该考虑遵循

  • 尽量避免使用废弃常量,推荐使用替代的、非废弃的常量。
  • 如果必须使用废弃常量,开发者应该了解其存在的问题和风险,并确保理解和处理这些问题。
  • 废弃常量可能在未来的版本中被移除,因此开发者应该及时更新代码,以避免依赖于已移除的常量。

无用的类

无用的类(Unused Classes) 是指在代码中存在但没有被使用或引用的类。这些类可能是代码重构、功能删除或其他变更导致的残留代码。

所需条件

类需要同时满足下面 3 个条件才能算是 无用的类

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。

  • 加载该类的 ClassLoader 已经被回收。

  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是可以,而并不是和对象一样不使用了就会必然被回收

造成的问题

在开发过程中,存在无用的类可能会造成一些问题

  1. 内存占用:无用的类会占用内存空间,增加应用程序的内存消耗。

  2. 代码冗余:无用的类会增加代码库的大小,导致代码冗余。

  3. 可读性下降:存在无用的类会使代码库变得混乱和不易维护。

解决步骤

解决无用类的问题,可以采取以下几个步骤

  1. 代码评审:定期进行代码评审,识别和删除未使用的类。
  2. 搜索引擎工具:使用搜索引擎工具(如IDE中的Find Usages)来检查类的引用情况,找出未被引用的类。
  3. 构建工具插件:使用一些构建工具插件(如ProGuard、Unused、UCDetector等),它们可以帮助 自动检测和删除未使用的类
  4. 清理过程:在代码库中进行定期的清理过程,包括删除未使用的类和其他无效代码。

在删除无用的类之前,应该先进行彻底的测试确保没有任何功能受到影响。一些类可能在特定的场景或条件下使用到,并且可能不容易通过简单的搜索来识别。

修复和删除无用的类有助于提高代码质量、减少资源浪费和简化维护工作。但同时也要谨慎操作,确保不会意外删除有用的类。

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

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

相关文章

Anaconda安装并配置Python环境

背景概述 Anaconda,中文大蟒蛇,是一个开源的Anaconda是专注于数据分析的Python发行版本,包含了conda、Python等190多个科学包及其依赖项。 Anaconda就是可以便捷获取包且对包能够进行管理,包括了python和很多常见的软件库和一个…

[OpenCV] 数字图像处理 C++ 学习——15像素重映射(cv::remap) 附完整代码

文章目录 前言1.像素重映射理论基础2.代码实现(1) remap()细节(2)水平翻转(2)垂直翻转(3)旋转 180 度(4)径向扭曲 3.完整代码 前言 像素重映射将图像中的每个像素映射到新位置,实现图像的扭曲、校正等操作。在 OpenCV 中,cv::remap() 函数就是用于实现这…

java实现系统文件管理

java实现系统文件管理 环境:jdk17springbootVueElementUI 背景:公司所做的项目需要别的系统向我们服务器上传文件,当我们需要查看这些文件什么时候上传的、文件数据是怎样的,只能去机房,排查问题效率较低,…

Redis复习笔记整理

目录 1、Redis简介 1.1 补充数据类型Stream 1.2 Redis底层数据结构 1.3 Redis为什么快 1.4 持久化机制* 1.4.1 RDB持久化 bgsave执行流程 如何保证数据一致性 快照操作期间服务崩溃 RDB优缺点 1.4.2 AOF持久化 为什么采用写后日志 如何实现AOF 什么是AOF重写 AO…

期末满分之模拟实现字符串函数

(一)strcpy 首先我们来了解该函数的使用方式 最简单的理解就是“复制粘贴”,比如现在有一个数组arr1,存放着 hello ;还有一个数组arr2,存放着 boy ;那么使用该函数之后,形如 strcpy&…

小米,B站网络安全岗位笔试题目+答案

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

mongoDB-1

文章目录 一、疑似坑1.11.2 mongo ops manager 一、疑似坑 1.1 https://www.bilibili.com/video/BV1H1421R7WD 2.x开始用,现在应该6.x了吧,早期四处鼓吹,为公司打造全mongo服务,为并发几千做了优化,原本打算替代MySQ…

【C++】——list

文章目录 list介绍和使用list注意事项 list模拟实现list和vector的不同 list介绍和使用 在C中,list是一个带头双向链表 list注意事项 迭代器失效 删除元素:当使用迭代器删除一个元素时,指向该元素的迭代器会失效,但是不会影响其他…

订单防重复提交:token 发放以及校验

订单防重复提交:token 发放以及校验 1. 基于Token校验避免订单重复提交 1. 基于Token校验避免订单重复提交 在很多秒杀场景中,用户为了能下单成功,会频繁的点击下单按钮,这时候如果没有做好控制的话,就可能会给一个用…

ElementUI 布局——行与列的灵活运用

ElementUI 布局——行与列的灵活运用 一 . 使用 Layout 组件1.1 注册路由1.2 使用 Layout 组件 二 . 行属性2.1 栅格的间隔2.2 自定义元素标签 三 . 列属性3.1 列的偏移3.2 列的移动 在现代网页设计中&#xff0c;布局是构建用户界面的基石。Element UI 框架通过其强大的 <e…

面向对象程序设计之继承(C++)

1.继承的定义 1.1继承的概念 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段&#xff0c;它允许我们在保持原有类特性的基础上进⾏扩展&#xff0c;增加⽅法(成员函数)和属性(成员变量)&#xff0c;这样产⽣新的类&#xff0c;称派⽣类。继承 呈现了⾯向…

Day26_0.1基础学习MATLAB学习小技巧总结(26)——数据插值

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a; 1、《MATLAB基础教程 (第三版) (薛山)》 2、《MATL…

Delphi CxGrid的主从表显示设置

界面编辑建立两个不同级别的视图层级-Layout 其实这是一个主从表关系&#xff0c; 1&#xff1a;填好主表的keyfieldnames 2&#xff1a;填好从表的keyfieldnames 3&#xff1a;填好从表的 detaikeyfieldNames与masterkeyfieldnames 4: 从表的数据源一定要按与主表关联的…

Vue实用操作-2-如何使用网页开发者工具

第一步&#xff0c;添加扩展&#xff0c;live服务器 第二步&#xff0c;将 favicon.ico 文件加入到根目录下 第三步&#xff0c;选择以服务器方式运行&#xff0c;并打开浏览器 第四步&#xff0c;在极简插件你中找到 vue 对应插件&#xff0c;安装到扩展插件中 第五步&#xf…

通过hosts.allow和hosts.deny限制用户登录

1、Hosts.allow和host.deny说明 两个文件是控制远程访问设置的&#xff0c;通过设置这个文件可以允许或者拒绝某个ip或者ip段的客户访问linux的某项服务。如果请求访问的主机名或IP不包含在/etc/hosts.allow中&#xff0c;那么tcpd进程就检查/etc/hosts.deny。看请求访问的主机…

【南方科技大学】CS315 Computer Security 【Lab2 Buffer Overflow】

目录 引言软件要求启动虚拟机环境设置禁用地址空间布局随机化&#xff08;ASLR&#xff09;设置编译器标志以禁用安全功能 概述BOF.ctestShellCode.c解释 createBadfile.c 开始利用漏洞在堆栈上查找返回地址 实验2的作业 之前有写过一个 博客&#xff0c;大家可以先看看栈溢出…

Qt ORM模块使用说明

附源码&#xff1a;QxOrm是一个C库资源-CSDN文库 使用说明 把QyOrm文件夹拷贝到自己的工程项目下, 在自己项目里的Pro文件里添加include($$PWD/QyOrm/QyOrm.pri)就能使用了 示例test_qyorm.h写了表的定义,Test_QyOrm_Main.cpp中写了所有支持的功能的例子: 通过自动表单添加…

C++——异常处理机制(try/catch/throw)

一、什么是异常处理机制 C++中的异常处理机制是一种用来检测和处理程序执行期间可能存在的异常情况的技术。它允许开发者编写健壮的代码,能够提前预判和处理程序执行可能会出现的错误,保证程序正常执行,而不会导致程序崩溃。 C++异常处理主要由几个关键字组成: try、cat…

C++笔记之std::map的实用操作

C++笔记之std::map的实用操作 code review 文章目录 C++笔记之std::map的实用操作1.初始化1.1.使用列表初始化1.2.使用 `insert` 方法1.3.使用 `emplace` 方法1.4.复制构造1.5.移动构造2.赋值2.1.列表赋值2.2.插入元素2.3.批量插入3.取值3.1.使用 `[]` 操作符3.2.使用 `at()` …

Vue路由配置、网络请求访问框架项目、element组件介绍学习

系列文章目录 第一章 基础知识、数据类型学习 第二章 万年历项目 第三章 代码逻辑训练习题 第四章 方法、数组学习 第五章 图书管理系统项目 第六章 面向对象编程&#xff1a;封装、继承、多态学习 第七章 封装继承多态习题 第八章 常用类、包装类、异常处理机制学习 第九章 集…