
Java程序员必看,JVM到底多重要?你踩过几个坑
最近找工作,刷了很多Java面试题,发现一个事儿。以前觉得写好代码就行,现在不行了,人家大厂面试,八股文一套一套的,尤其是JVM这块,基本是必问。我一开始也不当回事,结果连着被问懵了好几次,什么堆内存、GC回收、G1收集器,张嘴就卡壳。后来实在扛不住,下定决心把JVM从头到脚啃了一遍。
学之前,我以为JVM就是个跑Java程序的黑盒子,点一下运行就完事了。可真学了才发现,这东西不光管执行代码,还管内存分配、垃圾回收、线程调度。比如你new一个对象,它不是直接扔进内存就完了,而是在堆里开个地儿,还要分新生代和老年代。要是没搞懂这些,线上程序一出问题,你连日志都看不懂。
最让我头疼的是内存区域划分。以前只知道堆和栈,现在得记五个:堆、方法区、虚拟机栈、本地方法栈、程序计数器。堆是大家共享的,存对象实例;栈是每个线程私有的,方法调用一层层压进去,方法执行完就弹出来。我之前写了个递归,没控制好条件,栈直接爆了,报了个StackOverflowError,那时候还不知道是栈帧太多,现在回头看,全是基础不牢惹的祸。

还有方法区,存类的信息、静态变量、常量池这些东西。以前总听说永久代、元空间,听得一头雾水。后来才知道,JDK8之前叫永久代,之后改成了元空间,放到直接内存里了。要是JVM参数没配好,比如-XX:MetaspaceSize设太小,类加载多了就会OOM,线上服务突然挂掉,锅还得你背。
说到参数配置,简直是门技术活。-Xmx设多少合适?-Xms要不要和-Xmx一样大?新生代比例怎么分?我一开始随便写,结果压测一上,GC频繁得不行,服务卡成PPT。后来才知道,得根据应用特点来调。比如你做电商,秒杀时候对象创建飞快,就得把新生代整大点,减少Minor GC次数。
垃圾回收这块更是迷宫。四种算法:标记清除、复制、标记整理、分代收集。每种都有适用场景。标记清除效率低还容易碎片化;复制算法快但费空间;标记整理不产生碎片但停顿时间长。现在主流用分代收集,结合前面几种优点,开云app官方在线入口按对象存活周期分代处理。
{jz:field.toptypename/}
收集器就更多了。新生代有Serial、ParNew、Parallel Scavenge;老年代有Serial Old、Parallel Old、CMS;还有G1这种通吃整个堆的。CMS主打低延迟,但用的是标记清除,会有碎片;G1能预测停顿时间,还能分Region管理内存,比较适合大内存应用。我之前公司用的就是CMS,结果某次Full GC停了快两秒,用户投诉接口超时,查了半天才发现是碎片太多,被迫换成G1。
GC类型也得搞清。Minor GC发生在新生代,相对频繁但快;Major GC一般指老年代回收,有时候也泛指整个堆的清理;Full GC是整个堆和方法区都扫一遍,停顿时间最长。G1里又分Young GC、Mixed GC,不是按代叫了。要是分不清这些,看GC日志就跟看天书一样。
后来我学会了用工具。VisualVM、JConsole可以看内存使用、线程状态;MAT专门分析堆转储文件,找内存泄漏特别管用。有次线上服务内存疯涨,用MAT一查,发现是某个缓存没设过期,对象一直堆着不释放。改完之后,内存稳了,老板还夸我排查快。

调优也不是拍脑袋。一般先看应用是啥类型,是吞吐优先还是延迟敏感。然后观察GC日志,用-XX:+PrintGC、-XX:+PrintGCDetails这些参数打出来,再用工具分析。一步步调整堆大小、新生代比例、选择合适的收集器。我之前做过一个报表系统,数据量大但允许一定延迟,就选了Parallel Scavenge + Parallel Old组合,吞吐量上来了,效果不错。
整个学下来,花了一个多月,中间记了好几本笔记。现在再被问JVM,至少能说出个一二三,不像之前只会说“自动回收”这种空话。虽然还没到专家水平,但至少线上出问题,能定位是不是JVM的事,不至于干瞪眼。
说白了,JVM这东西,你不碰它好像也没事,一碰就全是坑。尤其现在Java岗位卷得厉害,谁还没点JVM知识,简历都不好意思投大厂。很多人觉得平时用不到,可真到面试或者线上救火的时候,就知道差别在哪了。

这系列文章我算是攒齐了,从内存结构到回收算法,从收集器到调优实战,还有工具使用,基本覆盖了日常能遇到的大部分问题。文档我也整理好了,放文末了,有需要的自取。反正学都学了,不如分享出来,省得后面的人像我一样,一个个坑去踩。
