运行时数据区域

JVM在运行Java程序的过程中会把管理的内存分成不同的数据区域 JVM 线程私有:

  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

线程共享:

  • 方法区(元空间)
  • 直接内存

虚拟机栈

Java虚拟机栈是一个线程私有的内存区域,用于存储每个方法执行时的局部变量表、操作数栈、动态链接、方法返回地址。每个方法在执行时都会创建一个栈帧,当方法执行完毕后,对应的栈帧将会出栈销毁。 局部变量表主要存放编译器可知的各种数据类型(基本数据类型)和对象引用 操作数栈用于在方法执行过程中进行数据操作和计算。操作数栈采用栈的数据结构,用于存储方法执行过程中的局部变量、中间结果和操作数。 动态链接是指在方法调用过程中进行方法的查找和链接,实现方法的动态绑定。主要用于支持Java程序中多态性和动态方法调用 方法返回地址是指在方法调用过程中,用于记录方法返回的地址信息。当一个方法被调用时,JVM会为该方法创建一个新的栈帧,并将其推入虚拟机栈中。

本地方法栈

本地方法栈与虚拟机栈类似,不同的是它主要是为执行本地方法服务(Native Method 使用C/C++编写的方法)。

程序计数器

程序计数器是一块比较小的内存空间,用于记录当前线程执行的字节码指令地址。当线程执行Java方法时,程序计数器用于指向下一条要执行的指令。

堆是JVM中最大的一块内存,用于存储对象实例和数组。堆被所有线程共享,因此在创建Java对象时,对象实例存储在堆中。 在JDK7及之前的版本中,堆主要分为三部分:

  1. 新生代
  2. 老年代
  3. 永久代

在JDK8之后,永久代已经被元空间取代,元空间使用的是本地内存。 Heap

对象首先都会在Eden区进行分配,在经过一次Young GC之后,如果对象还存活,则会进入from或者to,并且对象的年龄也会随之增加,在对象的年龄达到一定的程度之后,对象将会进入老年代

字符串常量池

字符串常量池是Java中一个特殊的内存区域,用于存储字符串常量。位于堆内存中,并在在JVM启动时就被创建。字符串常量池是JVM为了提升字符串的性能和减少内存消耗而开辟的一块区域,主要避免字符串重复创建 Heap 字符串常量池的主要特点:

  1. 字符串常量共享:字符串常量池中的字符串对象是唯一的,即相同的字符串字面量只会在常量池中存储一份。这是通过使用字符串的intern()方法将字符串对象添加到常量池中实现的。
  2. 提高性能和节省内存:由于字符串常量池中的字符串对象共享,因此当多个字符串字面量具有相同的内容时,它们可以引用一个对象,避免重复创建字符串对象,从而提高性能并节省内存。
  3. 存储在堆中:尽管字符串常量池位于堆中,但与普通的Java对象不同,字符串常量池中的字符串对象是在编译期间确定并加载到常量池中的。它们在程序运行期间一直存在,不会被来及回收

方法区(元空间)

方法区是线程共享的内存区域,用于存储类的类信息、字段信息、方法信息、常量、静态变量、编译器编译后的代码等数据。JDK 8及之前版本中方法区又被称为“永久代”(Permanent Generation),但在JDK 8之后已经被改为“元空间”(MetaSpace)。 永久代被移除的原因:

  1. 永久代内存不足导致内存溢出:永久代中存储的内容会随着应用的启动和停止不断增加,直到达到 PermGen 区的上限。在某些应用中,如果没有进行充分的调优,就很容易导致 PermGen 内存溢出。
  2. 调优复杂:由于 PermGen 区本质上是 JVM 堆区的一部分,因此在进行内存调优时非常麻烦。而且需要根据具体应用情况计算出合理的内存大小,比较繁琐。
  3. 元空间无需考虑内存大小:元空间是 Java 虚拟机内部的一块本地内存,不再使用 JVM 堆区的永久代,可以自动调整大小。这样就避免了内存溢出和调优的烦恼。
  4. 元空间允许热部署:元空间还可以支持热部署,也就是在运行期间动态更新类的定义,而不需要重新启动应用。

运行时常量池

运行时常量池是方法区的一部分,用于存储编译器生成的各种字面量和符号引用。每个类都有自己的运行时常量池,其中包含了该类所使用的常量、变量和方法的符号引用等信息。 运行时常量池的特点:

  1. 动态性:运行时常量池是在类加载过程中动态创建的。在加载过程中,字节码文件中的常量会被解析和转换成具体的内存结构,并存放在运行时常量池中。
  2. 独立性:每个类都有自己的运行时常量池,它与其他类的运行时常量池相互独立,存储着该类的常量信息。
  3. 存放常量:运行时常量池主要用于存放类文件中的常量。这些常量包括字符串常量、字面量和符号引用。通过运行时常量池,可以在运行时通过索引或其他方式访问和使用这些常量。
  4. 加速性能:运行时常量池可以提高程序的执行效率。在类加载的过程中,一些常量会被提前解析和计算,并存放在运行时常量池中。这样,在实际运行过程中可以直接使用运行时常量池中的常量,避免了重复计算和解析的开销。

直接内存

JVM中的直接内存是一种使用堆外内存(Off-heap)来实现的内存分配方式,也称为堆外内存(Off-heap Memory Allocation)。 直接内存的优势:

  1. 无需垃圾回收:Java堆内存需要进行垃圾回收,会对程序的性能产生一定影响。而直接内存不受JVM堆空间大小限制,无需进行垃圾回收,避免了大量的垃圾回收操作,从而提高了程序的性能和稳定性。
  2. 更高的访问速度:由于直接内存在堆外分配,因此不会占用JVM堆空间,也不会受到JVM堆空间大小的限制。同时,由于直接内存采用了DMA(直接内存访问)技术,可以实现更高效的读取和写入操作。
  3. 更大的空间:由于直接内存位于堆外,因此不受JVM堆大小的限制,可以使用更多的内存空间。并且,直接内存的分配和释放速度快,不会受到堆内存碎片等问题的影响,从而可以更好地支持大规模的、高性能的应用。