专栏简介 「为什么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掌控力!
计数机制:每个对象维护一个引用计数器,记录有多少引用指向该对象。
可能带来循环引用问题。无法处理对象间相互引用但整体不可达的情况

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

哪些对象可以作为GCRoot根对象?
我们采用如下图的MAT工具。

demo如下

运行代码。
启动jmap抓取当前内存快照。
先用jps命令找到进程id

再用jmap抓取当前内存快照

jmap:JDK自带的JVM内存分析工具-dump:触发堆转储操作format=b:指定输出为二进制格式(兼容MAT等分析工具)live:仅转存活对象(会触发Full GC)file=1.bin:输出到当前目录的1.bin文件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进行垃圾回收。

java中有四种引用。
实际上,常用的有五种。

看下下面这张图。

JVM的四种引用类型简介
JVM提供了四种不同强度的引用类型,用于更灵活地管理对象生命周期和内存回收:
JVM的四种引用类型及其适用场景:
强引用(Strong Reference),有GCRoot引用的对象
软引用(Soft Reference),没有被直接的强引用的对象
弱引用(Weak Reference)
虚引用(Phantom Reference)
软引用、弱引用都会配合引用队列使用。当他们引用的对象被回收时,软、弱引用都会进入回收队列。这是因为,软、弱引用,本身也会占用一定的内存,如果你想对它们占用的内存进行释放,需要通过引用队列来找到它们。需要回收时,依次遍历引用队列即可。

虚引用被创建时,也会关联一个引用队列。
在讲直接内存时,我们说过,创建ByteBuffer时,会创建一个名为Cleaner的虚引用对象。并注册到ReferenceQueue。
ByteBuffer会使用直接内存,并且会把直接内存地址传递给虚引用对象Cleaner。

当ByteBuffer没有强引用了,它会被垃圾回收掉。但直接内存并不能被JVM的垃圾回收所管理。GC后,cleaner会被加入关联的ReferenceQueue。
引用队列会定期被线程检测扫描,线程检测到队列中的虚引用后,调用绑定的Cleaner执行清理。

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

参考下面案例。

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

这是生产中很常见的场景,比如你存网上的图片。
所以,这个时候就很适合用软引用。尤其是在内存敏感的业务场景中。

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

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

怎么才能实现清理无用的软引用呢?
当然是引用队列了。

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

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