理解Java内存模型是对于开发、部署、监控、测试和调优Java应用程序的严肃开发人员来说是必要的学习。在这篇博客文章中,我们将讨论Java内存模型以及JVM内存的每个部分如何为我们的程序运行做出贡献。
首先,请检查你是否理解以下JVM体系结构图。如果你不熟悉它,我强烈建议你浏览一下我的上一篇文章(“Java生态系统(第1部分):理解JVM体系结构”),并刷新你的知识。

JVM体系结构
JVM内存模型
当运行资源密集型Java程序时,你必须使用以下某些JVM内存配置。
- -XmsSetting — 初始堆大小
- -XmxSetting — 最大堆大小
- -XX:NewSizeSetting — 新生成堆大小
- -XX:MaxNewSizeSetting — 最大新生成堆大小
- -XX:MaxPermGenSetting — 永久代最大大小
- -XX:SurvivorRatioSetting — 新堆大小比例(例如,如果Young Gen大小为10m,内存开关为–XX:SurvivorRatio=2,则为Eden空间保留5m,为两个Survivor空间各保留2.5m,默认值为8)
- -XX:NewRatio — 提供Old / New Gen大小比率(默认值= 2)
但是,你是否曾经想过JVM如何驻留在内存中?让我展示一下。与任何其他软件一样,JVM消耗主机操作系统内存中的可用空间。

主机操作系统内存和JVM
但是,在JVM内部,存在单独的内存空间(堆、非堆、缓存),以存储运行时数据和编译的代码。
1) 堆内存
- 堆被分为2部分 — Young Generation 和 Old Generation
- JVM启动时分配堆(初始大小:-Xms)
- 应用程序运行时,堆大小会增加/减少
- 最大大小:-Xmx

JVM堆内存
1.1) Young Generation
- 这是用于包含新分配的对象的空间
- Young Gen包括三个部分 — Eden Memory 和两个Survivor Memory spaces (S0, S1)
- 大多数新创建的对象都放在Eden空间中。
- 当Eden空间填满对象时,会执行Minor GC(又称Young Collection),并将所有幸存对象移动到一个幸存者空间中。
- Minor GC还检查幸存对象并将它们移动到另一个幸存者空间中。因此,一次只有一个幸存者空间为空。
- 在经过多个GC周期幸存的对象被移动到Old generation内存空间。通常是通过设置年轻代对象年龄的阈值来完成的,超过该阈值后,它们就有资格晋升为Old generation。
1.2) Old Generation
- 这是用于包含长期存活的对象的空间,它们可以在许多轮Minor GC后存活
- 当Old Gen空间已满时,会执行Major GC(又称Old Collection)(通常需要更长的时间)
2) 非堆内存
- 这包括Permanent Generation(自Java 8以来已由Metaspace替换)
- Perm Gen存储每个类的结构,例如运行时常量池、字段和方法数据以及方法和构造函数的代码,以及interned字符串
- 可以使用-XX:PermSize和-XX:MaxPermSize更改其大小

JVM非堆和缓存内存
3) 缓存内存
- 这包括Code Cache
- 存储JIT编译器生成的编译代码(即本机代码)、JVM内部结构、加载的分析器代理代码和数据等。
- 当Code Cache超过阈值时,它会被刷新(并且对象不会被GC重新定位)。
栈与堆
到目前为止,我没有提到Java栈内存,因为我想单独突出它的区别。首先,看一下下面的图像,看看这里发生了什么。我已经在我的上一篇文章中讨论了JVM Stack。

JVM Stack、非堆和堆(图片来源:jamesdbloom.com)
总之,Java栈内存用于执行线程,并且它包含方法特定值和堆中其他对象的引用。让我们将栈和堆放入表格中,看看它们的区别。

以下是一个不错的例子(来自baeldung.com),展示了栈和堆如何共同执行简单程序(使用代码检查堆栈顺序)。
class Person {
int pid;
String name;// constructor, setters/getters
}public class Driver {
public static void main(String[] args) {
int id = 23;
String pName = "Jon";
Person p = null;
p = new Person(id, pName);
}
}

Java中的栈内存和堆空间(图片来源:baeldung.com)
修改
上述Java内存模型是最常讨论的实现。但是,最新的JVM版本有不同的修改,例如引入以下新的内存空间。
- Keep Area — Young Generation中的一个新内存空间,用于包含最近分配的对象。在下一次Young Generation之前不执行GC。该区域防止对象被提升,只是因为它们在Young collection开始前被分配。
- Metaspace — 自Java 8以来,永久代已被Metaspace替换。它可以自动增加其大小(高达底层操作系统提供的大小),即使Perm Gen始终具有固定的最大大小。只要类加载器存在,元数据就会保留在Metaspace中,并且无法释放。# 内存相关问题
当发生严重的内存问题时,JVM 会崩溃并在你的程序输出中抛出以下错误指示。
- java.lang.StackOverFlowError — 表示栈内存已满
- java.lang.OutOfMemoryError: Java heap space — 表示堆内存已满
- java.lang.OutOfMemoryError: GC Overhead limit exceeded — 表示 GC 达到了其超额限制
- java.lang.OutOfMemoryError: Permgen space — 表示永久代空间已满
- java.lang.OutOfMemoryError: Metaspace — 表示元空间已满(自 Java 8 开始)
- java.lang.OutOfMemoryError: Unable to create new native thread — 表示 JVM 原生代码无法从底层操作系统中创建新的本机线程,因为已经创建了太多线程,并且它们消耗了 JVM 可用的所有内存
- java.lang.OutOfMemoryError: request size bytes for reason — 表示应用程序已完全消耗了交换内存空间
- java.lang.OutOfMemoryError: Requested array size exceeds VM limit — 表示我们的应用程序使用的数组大小超过了底层平台允许的大小
然而,你必须彻底了解的是,这些输出只能表示 JVM 的影响,而不是实际错误。实际错误及其根本原因可能发生在你的代码中的某个位置(例如,内存泄漏、GC 问题、同步问题)、资源分配,甚至可能是硬件设置。因此,我不能建议你仅仅增加受影响的资源大小来解决问题。也许你需要监控资源使用情况、对每个类别进行分析、查看堆转储、检查和调试/优化代码等。如果你的所有努力似乎都无效,并且你的上下文知识表明你需要更多的资源,请使用更多的资源。
参考资料
- JVM 内部 — http://blog.jamesdbloom.com/JVMInternals.html
- Java 中的栈内存和堆空间 — https://www.baeldung.com/java-stack-heap
- Java(JVM)内存模型 — Java 的内存管理 — https://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java
- Java 虚拟机(JVM)的内存管理 — https://betsol.com/2017/06/java-memory-management-for-java-virtual-machine-jvm/
译自:https://medium.com/platform-engineer/understanding-java-memory-model-1d0863f6d973








评论(0)