JVM深入浅出(1)--- 虚拟机内存区域

1. 运行时数据区域

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

其中,虚拟机栈,程序计数器,本地方法栈是线程私有的。方法区,堆是被所有线程共享的。

image-20260213205521738

1.1 程序计数器

程序技术器是线程私有的,指向当前执行的字节码指令地址,字节码解释器通过改变程序计数器的值来实现分支,循环,跳转。也是运行时数据区唯一不会发生OutOfMemoryError的地方。

1.2 虚拟机栈

虚拟机栈也是线程私有的。虚拟机栈是由一个个的栈帧组成的。
虚拟机栈也是线程私有的。虚拟机栈是由一个个的栈帧组成的。

栈帧包括了

  • 局部变量表

    • 局部变量表中的数据存储是一个又一个的局部变量槽(slot),局部变量表储存了java虚拟机的基本数据类型(byte,boolean,short,int,long,float,double,char),对象引用(不是对象本身)和returnAddress(字节码指令的地址),其中8字节的double和long占用两个局部变量槽。
  • 操作数栈

  • 动态连接

  • 方法出口

  • ...

虚拟机栈中可能会发生OOM和stackoverflow两种error

  • 当栈的深度超过虚拟机允许的深度时 --- > StackOverflow
  • 当栈是能动态扩容,且扩容后申请不到足够的内存 --> OOM

1.3 本地方法栈

本地方法栈和虚拟机栈类似,区别在于虚拟机栈执行的是java方法,而本地方法栈则是虚拟机使用到的native方法。

同样,本地方法栈也会跟虚拟机栈一样的情况抛出 StackOverflow 和OomError

1.4 Java 堆

  • 首先,Java堆中的数据是所有线程共享的
  • 主要是用来存储对象实例的。
  • 当代垃圾收集器大多根据分代收集理论设计,所以在堆中经常可以看到eden区,新生代,老年代,From survivor,To survivor 等等。
  • 当内存不够存储对象时,会发生OOMerror。
  • 堆中可以分配多个TLAB(thread local allocate buffer),用于给单个线程分配对象,这也是一种处理多线程分配对象的手段(还有乐观锁CAS

1.5 方法区

方法区也是各个线程共用的,主要是存储一些类元信息,常量,静态变量...

在Java8之前,方法区被称为永久代,这是因为当时垃圾收集器的分代设计拓展到方法区。在JDK7 之前,运行时常量池,字符串常量池,这些都是放在方法区中的,但是之后被转移到了堆中。到了JDK8,就没有了永久代的概念,转而变成了元空间

这块的内存回收主要是类的卸载(难以出现)。

当方法区的空间不足以存储新的数据,则报出OOM error。

1.6 运行时常量池

运行时常量池是属于方法区的,但是在内存中他和字符串常量池一样,是放在堆中的。

class文件中有常量池,在class文件编译之后,符号引用,字面量等等会被放到运行时常量池中。不单单针对编译过程,在程序运行的过程中,产生的常量也可以放到常量池中 比如String.intern()

运行时常量池不单单存储符号引用,也会把符号引用翻译出的直接引用也存储到运行时常量池中。

当运行时常量池无法处理新的内存分配时候,同样会报OOM error ,这一点和方法区是一样的。

1.7 直接内存

直接内存不属于运行时数据区。 直接内存是针对NIO 类,NIO类可以使用native函数库分配堆外内存,然后通过堆中的DirectByteBuffer对象作为这块内存的引用来操作这块内存。

直接内存也同样存在OOM error问题,因为其收到本机内存大小影响(通常较难排查)。

posted @ 2026-04-06 12:28  不会coding的喵酱  阅读(3)  评论(0)    收藏  举报