JVM 内存结构 详解

发布于:2025-06-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

内存结构

运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

程序计数器

​ 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。

每个线程都有一个程序计数器,由于线程之间会切换,因此为了线程切换后能恢复到正确的执行位置,就需要有程序计数器,记录正在执行的指令的地址。

​ 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空。程序计数器是线程私有的一小块内存区域,并且不会发生OOM

在这里插入图片描述

虚拟机栈

每个线程都有一个虚拟机栈,它的生命周期和线程相同,Java的方法调用是通过栈来实现的,一个栈由多个栈帧组成,当有一个方法调用就会有一个栈帧压入,方法调用结束栈帧就会弹出。

  • 局部变量表:在方法执行过程中存放所有的局部变量。

    ​ 局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

    在这里插入图片描述

  • 动态链接:方法中要用到其他类的属性和方法,这些内容在字节码文件中是以编号保存的,运行过程中需要替换成内存中的地址,这个编号到内存地址的映射关系就保存在动态链接中。

  • 操作数栈:操作数栈是栈帧中虚拟机在执行指令过程中用来存放临时数据的一块区域。临时存放操作数以及计算结果,最大深度在编译的时候就确定了

  • 方法返回地址:方法执行完毕后返回的地址

本地方法栈

线程私有,和虚拟机栈作用类似,本地方法栈存储的是native本地方法的栈帧。

Java堆:

​ ● 一般Java程序中堆内存是空间最大的一块内存区域。创建出来的对象都存在于堆上。

​ ● 栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。

​ 线程共享,几乎所有的对象都在堆上分配(逃逸分析可能导致对象在栈上分配)

​ 线程分配缓冲区:线程私有,提升对象分配时的效率

方法区

线程共享,存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

运行时常量池
运行时常量池是方法区的一部分。
通过javap反编译class文件,除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),存放了编译期就可以确定的字面量以及类、方法、字段的符号引用,这里的符号引用只是一个名称,不是真正的地址。
当类被加载时(具体为解析阶段),常量池表会被存放在运行时常量池里,然后把符号引用替换成真实地址引用

字符串常量池
字符串常量池是为了避免重复创建字符串对象,在JDK1.8之前,放在永久代里,到了1.8,放到了堆里。
为什么要将字符串常量池从永久代转移到堆里?永久代垃圾回收效率低,放在堆中垃圾回收效率更高。
字符串常量池是一个HashTable结构,key是字符串的符号,value是字符串对象的引用,指向堆。

在这里插入图片描述

直接内存(可选)

直接内存并不在《Java虚拟机规范》中存在,所以并不属于Java运行时的内存区域。在 JDK 1.4 中引入了 NIO 机制,由操作系统直接管理这部分内容,主要为了提升读写数据的性能。在网络编程框架如Netty中被大量使用。

要创建直接内存上的数据,可以使用ByteBuffer。

语法: ByteBuffer directBuffer = ByteBuffer.allocateDirect(size);

直接内存是用户态和核心态都可以访问的一块区域,通过Unsafe类来手动释放的