首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JVM揭秘之旅:打破性能瓶的终极指南(5)

JVM揭秘之旅:打破性能瓶的终极指南(5)

作者头像
半旧518
发布2025-07-10 08:46:48
发布2025-07-10 08:46:48
1100
举报
文章被收录于专栏:半旧的技术栈半旧的技术栈

专栏简介 「为什么Java程序员必须啃透JVM?」 JVM是Java生态的“灵魂引擎”,但多数开发者仅停留在API调用层面。当面临频发GC卡顿、诡异OOM崩溃或线程死锁顽疾时,是否曾因底层原理的模糊而束手无策?本专栏将带您穿透技术迷雾,系统攻克JVM核心领域:

⚙️ 硬核原理拆解:从字节码执行、类加载双亲委派,到G1/ZGC回收器设计,逐层剖析JVM的运作机制; 🛠️ 调优实战手册:结合大厂案例,详解参数配置(如-XX:+HeapDumpOnOutOfMemoryError)、内存泄漏定位(MAT工具)、并发瓶颈破解; 🚀 前沿技术追踪:涵盖元空间、JIT编译、协程(Loom项目)等新特性,提前掌握未来技术栈; 💡 面试高频攻略:深度解析京东/华为等大厂JVM面试题(如“CMS与G1的权衡”“内存屏障作用”)6,8。 适合读者: ✅ 渴求突破CRUD的Java工程师 ✅ 被性能问题困扰的架构师 ✅ 备战P7/P8级技术面试的求职者

专栏承诺:不用空洞理论堆砌,每篇均附可复现的代码案例及调优脚本。跟随专栏,您将获得从“被动救火”到“主动防御”的JVM掌控力!

1、如何判断对象是否可回收

如何判断对象是否可回收

引用计数法

计数机制:每个对象维护一个引用计数器,记录有多少引用指向该对象。

  • 当有新的引用指向对象时,计数器加1
  • 当引用被删除或指向其他对象时,计数器减1
  • 回收时机:当计数器变为0时,表示对象不再被引用,可以立即回收其内存

可能带来循环引用问题。无法处理对象间相互引用但整体不可达的情况

早期python虚拟机采用引用计数法,jvm虚拟机没有采用引用计数法。

可达性分析算法

可达性分析算法是一种追踪对象引用链的垃圾回收算法。从GC Roots(如全局变量、栈帧变量等)出发,标记所有可达对象为存活,其余不可达对象判定为垃圾并回收。相比引用计数法,它能处理循环引用问题,但需要暂停程序运行(Stop-The-World),常用于Java等语言。

就像一串葡萄。在串上的不可以被回收,散落的可以被回收。

根对象

哪些对象可以作为GCRoot根对象?

我们采用如下图的MAT工具。

demo如下

运行代码。

启动jmap抓取当前内存快照。

先用jps命令找到进程id

再用jmap抓取当前内存快照

  1. jmap:JDK自带的JVM内存分析工具
  2. -dump:触发堆转储操作
  3. format=b:指定输出为二进制格式(兼容MAT等分析工具)
  4. live:仅转存活对象(会触发Full GC)
  5. file=1.bin:输出到当前目录的1.bin文件
  6. 21384:目标Java进程PID

回车,再次抓取,进行对比。只修改存储的文件名。

通过eclipse中MAT工具打开转储的两个文件。

查看gc roots

可以看到根对象有以下几种。

第一类,核心类。JVM 运行时的核心对象必须存活,否则会导致虚拟机崩溃。

第二类。native stack。JNI(Java Native Interface)调用的本地方法可能引用Java对象,这些对象不能被回收。

第三类,Busy Monitor,正在被锁定的对象不能被回收,否则会导致并发问题。

第四类,活动线程。线程正在运行的方法引用的对象必须存活,否则会导致程序逻辑错误。

我们挑主线程实际看看。

注意,这里需要区分下引用和对象。

可以看到。下面框出来的就是我们代码中的ArrayList对象。他就是一个GCRoot。

也就是说,在我们当前活动的线程执行过程中,局部变量所引用的对象可以作为GCRoots。同样的,方法对象所引用的对象也可以作为GCRoots。

我们现在切换到第二个转储文件。同样的方法,查看GCRoots,找到主线程。

现在找不到ArrayList了。

这是因为我们代码中已经把它指向null了

而且,我们制定的jmap命令包含live参数,会触发GC进行垃圾回收。

10、四种引用类型

简介

java中有四种引用。

实际上,常用的有五种。

看下下面这张图。

JVM的四种引用类型简介

JVM提供了四种不同强度的引用类型,用于更灵活地管理对象生命周期和内存回收:

JVM的四种引用类型及其适用场景:

强引用(Strong Reference),有GCRoot引用的对象

  • 最常见的引用类型,通过new关键字创建的对象默认都是强引用
  • 只要强引用存在,垃圾回收器就永远不会回收该对象
  • 适用场景:普通对象创建,需要长期存在的对象
  • 示例:String str = new String(“hello”);

软引用(Soft Reference),没有被直接的强引用的对象

  • 当内存不足时会被回收,在抛出OutOfMemoryError之前
  • 适合用于实现内存敏感的缓存
  • 适用场景:缓存实现,如图片缓存
  • 示例:SoftReference softRef = new SoftReference<>(bitmap);

弱引用(Weak Reference)

  • 无论内存是否充足,只要发生垃圾回收就会被回收
  • 适合用于实现规范映射(canonicalizing mappings)
  • 适用场景:WeakHashMap、ThreadLocal中的Entry
  • 示例:WeakReference weakRef = new WeakReference<>(object);

虚引用(Phantom Reference)

  • 最弱的引用类型,无法通过虚引用获取对象实例
  • 主要用于跟踪对象被垃圾回收的状态
  • 适用场景:管理堆外内存,如NIO的DirectBuffer
  • 示例:ReferenceQueue queue = new ReferenceQueue(); PhantomReference phantomRef = new PhantomReference<>(object, queue);

软引用、弱引用

软引用、弱引用都会配合引用队列使用。当他们引用的对象被回收时,软、弱引用都会进入回收队列。这是因为,软、弱引用,本身也会占用一定的内存,如果你想对它们占用的内存进行释放,需要通过引用队列来找到它们。需要回收时,依次遍历引用队列即可。

虚引用

虚引用被创建时,也会关联一个引用队列。

在讲直接内存时,我们说过,创建ByteBuffer时,会创建一个名为Cleaner的虚引用对象。并注册到ReferenceQueue

ByteBuffer会使用直接内存,并且会把直接内存地址传递给虚引用对象Cleaner。

当ByteBuffer没有强引用了,它会被垃圾回收掉。但直接内存并不能被JVM的垃圾回收所管理。GC后,cleaner会被加入关联的ReferenceQueue

引用队列会定期被线程检测扫描,线程检测到队列中的虚引用后,调用绑定的Cleaner执行清理。

终结器引用

终结器引用(FinalReference)是JVM内部用于实现finalize()方法的特殊引用。当对象重写了finalize()且首次被GC标记为不可达时,JVM会将其放入终结器队列,由Finalizer线程异步执行finalize()。执行后对象会再次经历GC才能被回收。这种机制可能导致内存回收延迟,且无法保证执行顺序,因此在Java 9后被标记为废弃,建议使用Cleaner或虚引用替代。

软引用的应用

参考下面案例。

执行,会报一个内存溢出的错误。

这是生产中很常见的场景,比如你存网上的图片。

所以,这个时候就很适合用软引用。尤其是在内存敏感的业务场景中。

运行,并没有引起内存溢出。

不过好像有个问题。软引用对象引用的对象被垃圾回收了。但软引用还被放在list几何中呢。

怎么才能实现清理无用的软引用呢?

当然是引用队列了。

弱引用的应用

和软引用使用特别类似。这里不用太浪费时间赘述。看看栗子。

同样要配合队列使用,才能保证弱引用对象自身被回收。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-07-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、如何判断对象是否可回收
    • 如何判断对象是否可回收
      • 引用计数法
      • 可达性分析算法
  • 10、四种引用类型
    • 简介
    • 软引用、弱引用
    • 虚引用
    • 终结器引用
    • 软引用的应用
    • 弱引用的应用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档