代码跑着跑着就 OOM 了?带你手撕 JVM 内存模型,这回彻底搞懂对象到底住哪儿!
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
0. 前言:那些年被 JVM 毒打的岁月 🤕
回想当年我刚入行那会儿,觉得自己写的 Java 代码那是天衣无缝。直到有一天,生产环境的服务器突然不做声了,日志里只有一行触目惊心的红字:java.lang.OutOfMemoryError: Java heap space。当时我的冷汗唰地一下就下来了,第一反应就是重启(万能的重启大法),结果没过半小时,它又挂了!😭
那时候我不懂啊,心想内存不是自动回收的吗?咋还能爆呢?后来被大佬按头安利了 JVM 内存模型,我才恍然大悟:这就好比你开个饭馆,不知道哪里是厨房、哪里是库房、哪里是垃圾桶,这饭馆能不倒闭吗?
今天,咱们就抛开那些晦涩的官方文档,用咱们全栈都能听懂的“人话”,给 JVM 内存区域做个全方位的 CT 扫描!
1. 宏观鸟瞰:JVM 的“户型图” 🏠
首先,咱们得有个全局观。JVM 跑起来的时候,它把内存划分成了好几个区域。你可以把它想象成一个精细化管理的大豪宅。
这个豪宅里主要分两类地盘:
1. 私人领地(线程私有):只有你自己能进,别人进不来。比如:虚拟机栈、本地方法栈、程序计数器。这就像你的卧室,私密性极强,不需要考虑线程安全问题,你人走了(线程结束),这地儿也就清空了。
2. 公共大厅(线程共享):谁都能来踩一脚。比如:堆(Heap)、方法区(Method Area)。这就像客厅和厨房,张三能来,李四也能来,所以这里最容易出并发问题,垃圾回收(GC)主要也是在这儿忙活。
接下来,咱们一个屋一个屋地逛!🚶♂️
2. 虚拟机栈(VM Stack):你的私人工作台 🛠️
大家写代码时,最常用的就是方法调用。这个过程就在栈里完成。
我看很多教程说“栈管运行,堆管存储”,这话对,但不完全对。
栈是什么? 它是线程私有的。每当你调用一个方法,JVM 就会在栈里压入一个栈帧(Stack Frame)。这栈帧里存了啥?
- 局部变量表:你方法里定义的
int a = 1;就在这儿。 - 操作数栈:计算过程中的临时数据,比如
a + b的时候,数据就在这儿进进出出。 - 动态链接 & 方法出口:记录你从哪儿来,计算完要回哪儿去。
生动比喻:
这就好比你在厨房切菜(执行方法)。栈就是你的案板。你拿一个盘子(栈帧)放在案板上切土豆,切完了把盘子端走(出栈),再拿一个新盘子切西红柿(入栈)。这案板是你独享的,旁边的厨师(其他线程)不能乱拿你的刀。
💣 常见车祸现场:StackOverflowError
如果你的盘子堆得太高,顶到天花板了,那就爆栈了!通常发生在你写了死递归的时候。
👨💻 代码实战(请勿在生产环境尝试):
public class StackBoom {
private static int count = 0;
public static void recursiveMethod() {
count++;
// 没写终止条件的递归,这就是在玩火🔥
recursiveMethod();
}
public static void main(String[] args) {
try {
recursiveMethod();
} catch (StackOverflowError e) {
System.out.println("哎呀!栈爆了!💥");
System.out.println("就在第 " + count + " 层的时候扛不住了...");
// 栈深度是有限的,默认可能几千到几万层,看你JVM参数怎么设
}
}
}
3. 堆(Heap):乱糟糟的公共大仓库 📦
讲真,这是咱们跟 JVM 打交道最多的地方,也是最容易出幺蛾子的地方。
几乎所有你 new 出来的对象(new User(), new ArrayList()),都堆在这里。
堆的特点:
1. 大:它是内存里最大的一块地儿。
2. 乱:所有线程的对象都混在一起。
3. GC 的主战场:为了管理这么大的地方,JVM 把它分成了新生代(Young Gen)和老年代(Old Gen)。
生动比喻:
新生代就像是幼儿园,每天都有无数新对象(小孩)进来。大部分对象生命周期极短(朝生夕死),比如一个 HTTP 请求里的临时对象,用完就扔,所以新生代的 GC(Minor GC)非常频繁,像保洁阿姨一样天天扫地。
老年代就像是养老院。如果一个对象在幼儿园里经过了 15 次(默认值)大扫除还没被扫走,说明它是个“老顽固”,就会被移送进老年代。这里存的都是生命力顽强的常驻对象(比如 Spring 的 Bean,连接池对象)。
💣 常见车祸现场:OutOfMemoryError (Heap space)
如果养老院也住满了,保洁阿姨(Full GC)拼了老命也腾不出地儿来,那 JVM 只能两手一摊:挂了。
👨💻 代码实战(手写一个 OOM):
import java.util.ArrayList;
import java.util.List;
public class HeapBoom {
static class HeavyObject {
// 弄个大点的字节数组,占内存
byte[] data = new byte[1024 * 1024]; // 1MB
}
public static void main(String[] args) {
List<HeavyObject> list = new ArrayList<>();
System.out.println("开始往堆里疯狂塞东西...🚒");
try {
while (true) {
list.add(new HeavyObject());
// 这里加个Thread.sleep能让你看着内存一点点飙升,更刺激
// Thread.sleep(10);
}
} catch (OutOfMemoryError e) {
System.out.println("完犊子了!堆溢出!😱");
// 此时你的 Java 进程基本上也就废了
}
}
}
4. 方法区(Method Area):存放“蓝图”的档案馆 📚
这也是个共享区域。很多新手容易把它和堆搞混。
这里存的是类的信息(Class 结构)、常量、静态变量(static)。
简单说,堆里存的是房子(对象实例),而方法区里存的是房子的图纸(类定义)。你得先有图纸,才能盖房子,对吧?
这里有个重要的历史演变(敲黑板!):
* JDK 7 及以前:这块叫永久代(PermGen)。它其实在堆内存里偷了一块地。有个很大的坑是它大小固定,很容易满(PermGen space 错误),调优极难。
* JDK 8 及以后:Oracle 大手一挥,把永久代废了!改名叫元空间(Metaspace)。最屌的是,元空间不再占用 JVM 内存,而是直接使用本地物理内存!理论上,只要你物理内存够大,它就能无限膨胀(当然我们一般会设个上限)。
💣 常见车祸现场:Metaspace OOM
虽然改成了元空间,但如果你疯狂地动态生成类(比如用 CGLib 或动态代理狂搞),也会把它撑爆。
5. 直接内存(Direct Memory):不讲武德的 VIP 通道 🚀
这块区域其实不属于 JVM 运行时数据区的一部分,但咱们全栈开发必须得知道!
它是 NIO(Non-blocking IO)出现后才火起来的。
为什么需要它?
传统的 IO 操作(比如读文件),需要把数据从操作系统内核缓冲区复制到Java 堆内存,这中间有一次数据拷贝,多累啊!
直接内存就是直接在堆外分配内存,操作系统能访问,Java 也能直接操作(通过 DirectByteBuffer)。这就实现了零拷贝(Zero Copy),速度快得飞起!🚀 像 Netty 这种高性能框架,底层全是这玩意儿。
有啥缺点?
分配和回收的成本很高!而且因为它不受 GC 直接管理(虽然会有虚引用帮忙),如果用不好,很容易造成堆外内存泄漏,这种 Bug 查起来能让你怀疑人生。🤯
6. 总结:做个懂“风水”的开发 🧘♂️
你看,把 JVM 拆解开来看,是不是也没那么神秘?
咱们来个一句话总结:
栈是打工人的工位(私有、处理逻辑),堆是公司的仓库(共享、存数据),方法区是公司的规章制度墙(存类信息),直接内存是公司的特快专递通道(高性能 IO)。
了解这些有什么用?
当你下次遇到服务器变慢、CPU 飙高或者内存溢出时,你就不再是那个只会重启服务器的小白了。你会知道:
- StackOverflow?查查是不是有死递归。
- Heap OOM?查查是不是有大对象没释放,或者内存泄漏。
- Metaspace OOM?查查是不是动态加载了太多类。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!
- 点赞
- 收藏
- 关注作者
评论(0)