JVM揭秘之旅:打破性能瓶的终极指南(一)
专栏简介
「为什么Java程序员必须啃透JVM?」
JVM是Java生态的“灵魂引擎”,但多数开发者仅停留在API调用层面。当面临频发GC卡顿、诡异OOM崩溃或线程死锁顽疾时,是否曾因底层原理的模糊而束手无策?本专栏将带您穿透技术迷雾,系统攻克JVM核心领域:
- ⚙️ 硬核原理拆解:从字节码执行、类加载双亲委派,到G1/ZGC回收器设计,逐层剖析JVM的运作机制;
- 🛠️ 调优实战手册:结合大厂案例,详解参数配置(如
-XX:+HeapDumpOnOutOfMemoryError
)、内存泄漏定位(MAT工具)、并发瓶颈破解; - 🚀 前沿技术追踪:涵盖元空间、JIT编译、协程(Loom项目)等新特性,提前掌握未来技术栈;
- 💡 面试高频攻略:深度解析京东/华为等大厂JVM面试题。
适合读者:
✅ 渴求突破CRUD的Java工程师
✅ 被性能问题困扰的架构师
✅ 备战P7/P8级技术面试的求职者
专栏承诺:不用空洞理论堆砌,每篇均附可复现的代码案例及调优脚本。跟随专栏,您将获得从“被动救火”到“主动防御”的JVM掌控力!
1、JVM简介
什么是jvm
学习JVM有什么用
常见的JVM
以下JVM以Hotspot为准。
JVM的学习路线
2、程序计数器
程序计数器的作用
在物理上,程序计数器是通过寄存器实现的。
程序计数器的特点
线程私有。每个线程都有自己的程序计数器。不同线程抢cpu,抢到了,就根据程序计数器的地址,执行下一条代码。
不会存在内存溢出。
3、虚拟机栈
栈
栈:先进后出。
虚拟机栈:线程运行时需要的内存空间。
栈帧:每个方法运行时需要的内存。
栈的演示
栈的问题辨析
1.垃圾回收不涉及栈内存。因为方法结束,栈帧的生命就结束了。
2.栈内存大,可以进行递归调用的层数多,但程序可执行线程越少(总内存不变,栈内存大,线程数少)
详解:
对于基本数据类型,安全。
局部变量存在栈中,属于线程私有。
对于对象。
判断下列三个方法是否线程安全?
m1,显然安全。局部变量线程私有。
m2,不安全,形参是被传入的,其它线程也可有访问到sb。
m3,不安全。虽然sb是局部变量,但是被返回了,其它线程可以访问到。
栈内存溢出
1.栈帧过多。
实际一般是递归调用没有合理的递归终止条件。
有时候不是自己的代码有问题,而是没有正确引用第三方库。
比如转json时。
TypeScript import com.fasterxml.jackson.databind.ObjectMapper; class Dept { private String name; private Emp manager; // 构造方法、getter和setter public Dept(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Emp getManager() { return manager; } public void setManager(Emp manager) { this.manager = manager; } } class Emp { private String name; private Dept dept; // 构造方法、getter和setter public Emp(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } } public class StackOverflowDemo { public static void main(String[] args) throws Exception { // 创建相互引用的对象 Dept dept = new Dept(“研发部”); Emp emp = new Emp(“张三”); dept.setManager(emp); emp.setDept(dept); // 尝试序列化为JSON - 这将导致栈溢出 ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(dept); System.out.println(json); } } |
---|
正确做法。
TypeScript class Emp { // … @JsonIgnore public Dept getDept() { return dept; } } |
---|
2.栈帧过大导致内存溢出。一般不会出现。
线程运行诊断——cpu运行高
top命令定位到进程。
定位到线程。
H 表示以"线程模式"显示信息。默认 ps 只显示进程,加上 H 后会显示进程中的所有线程
-e 显示所有用户进程,包括其它用户进程。
-o 自定义输出格式
知道了线程编号,接下来。定位到代码中的线程名
先将线程id32665换算成16进制。
Jstack 进程id.
看到没,第8行代码。
线程死锁排查——程序运行很长时间无结果
nohub在后台运行代码。
本来应该输出结果,一直没有。可能是死锁。
上面运行代码,可以看到进程id 32752.
Jstack 32752.
报错信息很明显。
根据上面信息的代码行数定位下问题。
很明显,它们互锁了。