JVM实战(1)——理论基础篇:JAVA内存模型(JMM-java memory model)
了解Java内存模型是对于严肃的Java开发人员来说必不可少的学习内容,他们开发、部署、监控、测试和调优Java应用程序的性能。在本博文中,我们将讨论Java内存模型以及JVM内存的每个部分如何对我们的程序运行做出贡献。
首先,请检查您是否理解以下JVM架构图。如果您对此不熟悉,我强烈建议您浏览一下我之前的博文("Java生态系统(第一部分):理解JVM架构"),并更新您的知识。
JVM内存模型
在运行资源密集型Java程序时,您可能会使用以下一些JVM内存配置。
-Xms设置:初始堆大小
-Xmx设置:最大堆大小
-XX:NewSize设置:新生代堆大小
-XX:MaxNewSize设置:最大新生代堆大小
-XX:MaxPermGen设置:永久代的最大大小
-XX:SurvivorRatio设置:新生代堆大小比例(例如,如果Young Gen大小为10m且内存开关为-XX:SurvivorRatio=2
,则会为Eden空间保留5m,每个Survivor空间保留2.5m,默认值为8)
-XX:NewRatio:提供老年代和新生代大小的比例(默认值为2)
但是,您是否曾经想过JVM是如何存在于内存中的?让我来展示给您。就像其他任何软件一样,JVM占用主机操作系统内存中的可用空间。
然而,在JVM内部,存在着独立的内存空间(堆、非堆、缓存),用于存储运行时数据和编译代码。
1)堆内存
堆内存分为两个部分:新生代和老年代
堆内存在JVM启动时分配(初始大小:-Xms
)
应用程序运行时,堆内存的大小会增加或减小
最大大小:-Xmx
1.1) 新生代
这部分用于存放新分配的对象
新生代包括三个部分:伊甸园空间和两个幸存者空间(S0、S1)
大部分新创建的对象都存放在伊甸园空间中
当伊甸园空间填满对象时,会进行次要GC(也称为新生代回收),将所有幸存者对象移动到其中一个幸存者空间
次要GC还会检查幸存者对象,并将它们移动到另一个幸存者空间。因此,始终有一个幸存者空间是空的
经过多次GC循环后幸存下来的对象会被移动到老年代内存空间。通常,在年轻代对象达到一定年龄阈值之前,它们不会被提升到老年代。
1.2) 老年代
这部分用于存放经过多次次要GC后仍然存活的长生命周期对象
当老年代空间满时,会进行主要GC(也称为老年代回收),通常需要较长时间
2) 非堆内存
这包括永久代(在Java 8中被元空间(Metaspace)取代)
永久代存储每个类的结构,例如运行时常量池、字段和方法数据以及方法和构造函数的代码,以及字符串池中的字符串
可以使用-XX:PermSize
和-XX:MaxPermSize
来更改其大小
3) 缓存内存
这包括代码缓存
存储由JIT编译器生成的编译代码(即本机代码)、JVM内部结构、加载的分析器代理代码和数据等。
当代码缓存超过阈值时,它会被清空(对象不会被GC重新定位)。
栈与堆
到目前为止,我还没有提及Java堆栈内存,因为我想单独强调它的区别。首先,看一下下面的图像,看看你是否知道这里发生了什么。我在之前的文章中已经讨论过JVM堆栈。
总之,简而言之,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内存模型是最常讨论的实现方式。然而,最新的JVM版本进行了一些修改,引入了以下新的内存空间。
Keep区域:在Young Generation中新增的内存空间,用于存储最近分配的对象。在下一个Young Generation之前不会进行垃圾回收。这个区域可以防止对象因为在Young Collection开始前被分配而被提升。
Metaspace:自Java 8开始,Metaspace取代了Permanent Generation。它可以自动增加其大小(最高达到底层操作系统提供的限制),而Perm Gen总是有一个固定的最大大小。只要类加载器还存活,元数据就会保持在Metaspace中,并且无法释放。
注意:建议您阅读供应商文档,以了解适用于您的JVM版本的情况。
内存相关问题
当出现关键的内存问题时,JVM会崩溃并在程序输出中抛出以下错误提示。
java.lang.StackOverflowError — 表示堆栈内存已满
java.lang.OutOfMemoryError: Java heap space — 表示堆内存已满
java.lang.OutOfMemoryError: GC Overhead limit exceeded — 表示GC已达到其开销限制
java.lang.OutOfMemoryError: Permgen space — 表示Permanent Generation空间已满
java.lang.OutOfMemoryError: Metaspace — 表示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 调优实战
- 本文链接: https://www.v8en.com/article/324
- 版权声明: 本文由SIMON原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权