专栏简介 「为什么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几何中呢。
怎么才能实现清理无用的软引用呢?
当然是引用队列了。
和软引用使用特别类似。这里不用太浪费时间赘述。看看栗子。
同样要配合队列使用,才能保证弱引用对象自身被回收。