Java虚拟机的内存结构
一、JVM结构
二、JVM内存结构概览:

2.1 程序计数器
和物理机的程序计数器概念一致,用于确认当前线程执行代码的位置,在上下文切换时候会被推入栈中/从栈中恢复。为线程私有。
2.2 JVM堆和栈
JVM栈的是配置每个线程可以使用的内存大小,而不是所有线程总内存。对于每个线程来说,这个内存是私有的,主要存:1. 上下文切换时候的程序计数器 2. 超出指定寄存器数量的函数参数 3. 局部变量
2.2.1 StackOverflowError
当存储的上3种信息超过Xss(如大量不同函数发生上下文切换,递归层数过高)可能导致需要的内存数量超过Xss,导致StackOverflowError。
2.2.2 OutOfMemoryError
Xss会给每个线程分配固定大小的内存,当物理内存无法提供新的线程的Stack空间,就会出现OutOfM emoryError。
注意:StackOverflowError 和 OutOfMemoryError出现的场景是不同的,原因也是不同的,不可以把两者混为一谈,简单的称为”内存溢出“。StackOverflowError是单个线程纬度的,简单直白的说就是“程序发生过度的上下文切换,导致stack中保存了大量的上下文信息”,其排查方向一般是在程序层面:1. 递归调用2. 大量嵌套方法 3. 框架内部连续函数调用链过深。OutOfMemoryError是内存不足(包括单个线程堆内存不足Xsm、Xmx或者线程太多导致无法分配栈空间(Xss),也可能是系统内存不足),导致线程无法获取新的内存(即使这个线程是正常的线程)。
三、堆
堆的特点:
- 动态分配:当对象被创建时,JVM会动态分配内存到堆中。
- 垃圾回收:JVM堆内存会被垃圾回收机制管理,回收不再使用的对象。
- 区域划分:JVM堆通常会被分为年轻代(Young Generation)和老年代(Old Generation),以及永久代(PermGen)或元空间(Metaspace)(JVM 8后改变了PermGen的结构,改用Metaspace)。
堆的大小:JVM堆的大小可以通过启动参数来调整,常用的有:
- -Xms:设置堆的初始大小。
- -Xmx:设置堆的最大大小。
四、本地方法栈
本地方法栈是JVM栈的一部分,用于支持JVM调用本地方法(Native Methods)。本地方法是指用其他语言(如C、C++)编写的代码,这些代码通常是为了提高性能或者访问系统资源。
本地方法栈的特点:
- 执行本地代码:本地方法栈用于执行通过JNI(Java Native Interface)调用的C、C++等语言编写的代码。
- 线程私有:每个线程都有独立的本地方法栈。
- 和JVM栈的区别:JVM栈用于执行Java字节码,而本地方法栈则是为执行本地方法提供的内存区域。
本地方法栈的大小:本地方法栈的大小通常是有限制的,可以通过JVM参数-Xoss来设置栈的大小。
五、方法区
作用:方法区用于存储类的元数据(metadata),包括类的信息、方法的字节码、常量池等。它是JVM内存模型的一部分,包含了所有被加载的类和接口的相关数据。
内容: 类的结构:类的元数据(包括类名、字段、方法、接口等)存储在方法区中。 方法的字节码:类的方法的字节码也是存放在方法区的。 静态变量:静态成员变量也存储在方法区中,而非存储在堆中。 常量池:方法区中存放常量池,它包含了编译期生成的常量(例如字符串常量、数字常量等),而这些常量会在运行时被引用。
注意:JDK 8 及以后,方法区的概念被替代为“Metaspace”,Metaspace不再使用堆内存,而是直接使用本地内存(Native Memory)。这意味着,方法区的内容(如类的元数据)不再受堆内存大小的限制,但它仍然承担着类似的功能。
六、运行时常量池
作用:运行时常量池是方法区的一部分,用于存储类中的常量信息。常量池中的数据包括字面量(例如字符串字面量)以及符号引用(如类、方法、字段等的引用)。
特点: 常量池会在类加载时被创建并加载到JVM中,存储类中的常量数据(例如final变量、字符串字面量等)。 常量池的内容可以在运行时通过程序进行修改,尤其是通过字面量的解析。 常量池包括两部分: 字面量池:存储所有的字面量(例如字符串字面量、数值字面量等)。 符号引用池:存储一些符号引用,指向类、方法、字段等,可以在运行时动态解析。
运行时常量池的动态性:常量池的内容不仅仅来自类加载时的编译时常量,还可以在运行时动态添加(比如String.intern()方法)。

评论(0)