当前位置: 首页 > news >正文

【从零学习JVM|第八篇】深入探寻堆内存

前言:

     堆回收主要作用是清理Java堆中不再被引用的对象,释放内存空间,避免内存泄漏。它通过标记可回收对象,再用合适算法回收,保障内存高效利用。同时,堆回收机制影响程序性能,了解其原理能优化内存管理,提升应用稳定性和运行效率。

    堆是 Java 运行时环境的基石,其高效管理直接决定了应用的性能、稳定性和可扩展性。深入理解堆内存机制(如分代策略、GC 算法)是 Java 开发者必备的核心技能。

接下来让我们来深入探寻堆的内存机制。

引用计数法和可达性分析法

java堆有一个最大的特点就是,自动垃圾回收,这一部分不需要我们程序员来操心,那它怎么知道什么时候把垃圾清理掉,它怎么知道哪部分是垃圾?

在Java中的对象是否可以被回收,是根据对象是否被引用来决定的。如果对象被引用了,说明改对象还要使用,不能回收。

引用计数法

核心思想: 每个对象都自带一个“小计数器”。这个计数器记录着当前有多少个“用户”正在引用(使用)它。当一个新的用户开始使用它时,计数器加 1;当一个用户用完它时,计数器减 1。每次减一后java虚拟机都会进行扫描,当发现当计数器归零时,意味着没有任何用户需要它了,系统就可以立刻把它回收。

优点:

实时性:每次执行减一操作之后,都会进行扫描,一旦发现为0,立即回收。没有延迟!这对于需要及时释放资源(如文件句柄、数据库连接、网络端口)的场景非常有利。

实现相对简单:核心逻辑就是计数器加减和归零判断,概念清晰易懂。

缺点:

频繁的计数器更新开销:每次引用和取消引用都会维护计数器,对系统的性能会有一定的影响。

计数器存储开销:每个对象都会存储一个计数器,如果对象很多,那计数器也很多。

循环引用问题 (致命弱点!):比如,在堆中有两个对象,然后这两个对象互相引用,那他们的计数器永远都不会归零。永远都是1,它们就永远不可能被回收。

可达性分析算法

可达性分析算法(Reachability Analysis)是自动内存管理的核心算法,用于判定堆内存中的对象是否存活。其核心思想是:分为两个对象一个叫GC Roots,一个叫普通对象。通过一系列称为“GC Roots”的根对象作为起点,遍历对象引用链,所有能被遍历到的对象是“存活”的,不能被遍历到的对象是“垃圾”

GC Roots 是垃圾回收的起点,以下对象可作为 GC Roots:

  1. 虚拟机栈中的引用对象(如方法局部变量)
    理由:方法执行时栈帧中的变量正在使用,其引用的对象不能回收。

  2. 方法区中类静态属性引用的对象
    理由:静态变量随类加载存在,生命周期长,引用的对象需保留。

  3. 方法区中常量引用的对象
    理由:常量池中的常量引用的对象(如字符串常量)不能被回收。

  4. 本地方法栈中 JNI 引用的对象
    理由:本地代码(如 C++)正在使用的 Java 对象不能被回收。

  5. 活跃线程的引用对象比如
    理由:线程正在执行,其引用的对象不能回收。

一句话GC Roots 就是“程序当前绝对离不开的对象”,绝对不会被回收。

我们一直提到的有没有被引用,其实引用也分有类型。

五种引用类型

强引用 (Strong Reference):

Object obj = new Object()这种最常见的引用,obj引用了Object 在堆中的对象,只要强引用还在,就不可能被回收。如果obj=null,这个时候堆中的对象就会被回收了。

实现方式:普通对象引用

// 创建强引用
Object obj = new Object();// 解除强引用(使对象可回收)
obj = null; 

软引用 (Soft Reference):

它是相对于强引用弱一点的引用关系,软引用指向的对象,在 内存不足,会被垃圾回收器回收。然后把这部分内存分给其他对象。

软引用本身作为一个对象,也会被回收,但其回收条件与普通对象类似:当没有任何强引用指向该软引用对象时,就会被垃圾回收器回收。

实现类java.lang.ref.SoftReference

// 创建软引用
SoftReference<Object> softRef = new SoftReference<>(new Object());// 获取对象(可能返回 null)
Object obj = softRef.get(); // 使用引用队列(可选)
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> softRefWithQueue = new SoftReference<>(new Object(), queue);

软引用在 Java 中用于实现内存敏感的缓存,适用于以下场景:

  1. 图片缓存:存储图片资源,当内存不足时自动释放,避免 OOM。
  2. 网页缓存:缓存网页内容,在内存紧张时优先回收,提升性能。
  3. 大对象缓存:缓存计算结果、数据库查询结果等,减少重复计算。
  4. 内存敏感的中间数据:存储临时计算结果,在内存不足时优雅降级。

核心优势:在保证应用正常运行的前提下,最大限度利用内存,提升性能。

软引用通常和一个引用队列一起联用

引用队列 (ReferenceQueue) 的作用:

  • 创建软引用时传入一个引用队列

  • 当软引用指向的对象 被垃圾回收器回收后,这个 软引用对象本身会被 JVM 自动放入关联的队列中。

  • 程序可以定期检查这个队列,知道哪些软引用持有的对象已经被回收了,从而进行一些清理工作(例如从缓存映射中移除对应的键)。

弱引用 (Weak Reference):

弱引用和软引用的本质区别就是,不管内存够不够它所关联的对象在垃圾回收时都会被回收。它完全不影响它所关联对象存活。

弱引用通常和引用队列搭配使用,当对象被回收时,弱引用会被放入队列,程序可借此感知对象的生命周期。

  • 核心特点:弱引用关联的对象,在没有强引用时会被立即回收,适合需要 “自动过期” 的场景。
  • 与软引用的区别:软引用在内存不足时才会回收对象,而弱引用只要 GC 运行就会回收(不管内存是否充足)。

实现类java.lang.ref.WeakReference

// 创建弱引用
WeakReference<Object> weakRef = new WeakReference<>(new Object());// 获取对象(可能很快变 null)
Object obj = weakRef.get();// 典型应用:WeakHashMap
WeakHashMap<Key, Value> weakMap = new WeakHashMap<>();

为什么要用弱引用?

  1. 缓存场景:防止内存泄漏

    • 例子:HashMap 存储图片对象时,若用强引用,即使图片不再被使用,也无法被回收,导致内存占用过高。而用弱引用存储键或值,当图片不再被其他地方引用时,会被 GC 自动清理,避免缓存 “占着内存不放”。
  2. 解决回调导致的内存泄漏

    • 场景:GUI 程序中,窗口(Window)持有监听器(Listener)的强引用,若监听器又反向引用窗口,会形成循环引用。用弱引用定义监听器对窗口的引用,可让窗口在关闭后正常被回收。
  3. Map 结构的弱键设计

    • 比如WeakHashMap,它的键是弱引用:当键对象不再被强引用时,GC 会自动清理对应的键值对,适合实现 “临时关联” 的数据结构(如缓存自动过期)。

弱引用的特性是 “对象没有强引用时会被 GC 直接回收”,但程序无法主动知道 GC 何时回收对象引用队列的作用就是:

  • 当 GC 回收弱引用关联的对象时,JVM 会自动把这个弱引用对象放入队列;
  • 程序通过监听队列,就能得知 “对象已被回收”,从而执行后续处理(如删除缓存、更新状态等)。

虚引用 (Phantom Reference):

     虚引用是所有引用类型中最弱的,必须和引用队列(ReferenceQueue)一起用,对象被回收前,虚引用会被放入队列,此时程序可以做后续处理,但对象本身已经 “必死无疑” 了。

  • 存在的唯一目的:只是为了在对象被垃圾回收时,收到一个 “通知”,但完全不影响对象的生命周期。
  • 使用场景常用于管理堆外内存(比如 NIO 的 DirectBuffer)。当虚引用关联的对象被回收时,JVM 会把这个虚引用加入到一个队列里,程序可以通过监听这个队列,手动释放堆外资源(因为堆外内存不受 JVM 直接管理)。

实现类java.lang.ref.PhantomReference

// 必须配合引用队列使用
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);// 获取对象总是返回 null
Object obj = phantomRef.get(); // 始终为 null// 监控回收通知
Reference<?> ref = queue.remove(); // 阻塞直到有引用入队
if (ref == phantomRef) {// 对象已被回收,执行清理操作
}

虚引用和引用队列:

  1. 虚引用必须和引用队列绑定:创建虚引用时,必须传入一个引用队列实例。
  2. 对象回收时的通知机制:当虚引用关联的对象被垃圾回收器判定为可回收时,JVM 不会直接销毁这个对象,而是先把对应的虚引用放入引用队列。
  3. 程序的响应时机:程序可以通过轮询引用队列,一旦发现队列中有虚引用,就知道关联的对象即将被回收,此时可以执行后续操作(如释放堆外内存)。
  4. 避免资源泄漏:如果没有引用队列,虚引用就无法通知程序对象被回收的事件,导致程序无法及时处理堆外资源(因为堆外内存不归 JVM 管理),从而造成资源泄漏。

终结器引用 (Final Reference) :

  当对象没有强引用指向,被判定为可回收时,JVM 会先调用它finalizer方法(若重写了该方法)。这相当于让对象在 “被销毁前” 有机会执行一些清理操作,比如:

  • 释放本地资源(如 C 语言层面的句柄);
  • 断开与外部资源的连接(虽然不推荐,但早期有程序这么用);
  • 甚至通过重新建立强引用 “自救”,避免被回收。

实现机制:JVM 内部管理

// 创建引用队列
ReferenceQueue<Object> queue = new ReferenceQueue<>();// 与引用配合使用
WeakReference<Object> ref = new WeakReference<>(new Object(), queue);// 检查队列(非阻塞)
Reference<?> polledRef = queue.poll();// 阻塞等待
try {Reference<?> removedRef = queue.remove(); // 阻塞直到有引用入队
} catch (InterruptedException e) {Thread.currentThread().interrupt();
}

它的工作过程:

  1. 当垃圾回收器检测到对象仅被终结器引用(即没有其他强引用指向该对象)时,会将其放入终结队列(Finalizer Queue)。
  2. JVM 会启动一个低优先级的守护线程(Finalizer 线程),从队列中取出对象并调用其finalize()方法。
  3. 执行完finalize()后,对象在下一次 GC 时才会被真正回收。

但是在实际运用中不推荐

  • 执行时机不可控:JVM 何时调用finalize()完全不确定 —— 可能在对象可回收后很久才执行,甚至因 JVM 退出而不执行,导致资源长时间无法释放(比如文件句柄占用)。
  • 执行顺序混乱:对象的finalize()执行顺序与创建顺序无关,若多个对象存在依赖关系(如 A 依赖 B),可能出现 B 先被回收而 A 后回收的情况,导致逻辑错误。
  • 性能开销大:JVM 需要维护终结器引用队列,处理finalize()方法的调用,尤其当大量对象重写finalize()时,会显著影响垃圾回收效率。

并且我们需要注意的是finalize方法仅仅会被执行一次。

在常规开发中虚引用和终结器引用不会使用,了解即可。

软引用和弱引用可以不使用引用队列,但是虚引用必须使用。

总结

    堆内存是Java虚拟机中用于存储对象实例和数组的内存区域,是内存管理的核心部分。其主要作用包括:为对象实例分配内存空间,所有new创建的对象都在堆中存储;是垃圾回收的主要区域,JVM通过垃圾回收机制自动管理堆中对象的生命周期,回收不再被引用的对象以释放内存;支持动态内存分配,根据程序运行时的需求动态调整内存使用,满足对象创建和销毁的动态变化。堆内存的大小可通过JVM参数(如-Xms、-Xmx)配置,合理设置能优化内存使用和程序性能。

创作不易,如果这篇文章对你有帮助请点赞收藏加转发,感谢你的阅读,你的支持就是我最大的动力。

相关文章:

  • Android 开发中,Intent 和 Bundle 组件间传递数据的几种方式
  • RedHat主机配置日志留存策略:从4周延长至6个月
  • FramePack 与其他视频生成工具的横向对比:优势、短板与差异化竞争
  • GitHub 上 PAT 和 SSH 的 7 个主要区别:您应该选择哪一个?
  • DAY 52 神经网络调参指南
  • 小白讲强化学习:从零开始的4x4网格世界探索
  • C/C++内存分布和管理
  • 以楼宇自控技术赋能节能,驱动绿色建筑可持续发展进程
  • PCL 导入VS配置的大量依赖项名称快速读取
  • git报错fatal: 远端意外挂断了
  • 简述Unity的资源加载和内存管理
  • 【地图服务限制范围】
  • SAP ERS 自动化发票
  • 图像处理与机器学习项目:特征提取、PCA与分类器评估
  • 多参表达式Hive UDF
  • 达梦数据库中无效触发器的排查与解决方案指南
  • 【狂飙AGI】第2课:大模型方向市场分析
  • 第四讲 基础运算之小数运算
  • 无外接物理显示器的Ubuntu系统的远程桌面连接(升级版)
  • 深度学习编译器
  • wordpress许愿插件/哈尔滨seo关键词排名
  • 网站返回404是什么意思/网络营销软件代理
  • 网页设计图片大小如何调整/seo的基础是什么
  • 人才网招聘网官网/惠州百度seo找谁
  • 案例学——网页设计与网站建设/网站制作郑州
  • apple 网站模板/发外链的论坛