首页IT科技jvm运行时内存区域划分(JVM运行时数据区域详解)

jvm运行时内存区域划分(JVM运行时数据区域详解)

时间2025-09-19 03:40:16分类IT科技浏览5003
导读:参考文章: 《Java Se11 虚拟机规范》 《深入理解Java虚拟机-JVM高级特性与最佳实践 第3版》- 周志明...

参考文章:

《Java Se11 虚拟机规范》 《深入理解Java虚拟机-JVM高级特性与最佳实践 第3版》- 周志明

本文基于Java Se 11讲解              。

根据《Java虚拟机规范》的规定             ,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:

对于不同的虚拟机实现                    ,在运行时数据区的实现上并不完全相同                     。对于常用的HotSpot虚拟机来说        ,它的运行时数据区如下:

主要区别在于          ,HotSpot使用了直接使用本地内存(即机器本身内存)的元空间(metaspace)来实现方法区      。

下面针对每个具体的数据区域进行详细的介绍          。

1. 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间                   ,它可以看作是当前线程所执行的字节码的行号指示器                      。

JVM可以同时支持多个执行线程         。每个Java虚拟机线程都有自己的pc(程序计数器)寄存器      。在任何时候            ,每个Java虚拟机线程都在执行单个方法的代码       ,即该线程的当前方法                      。如果该方法不是native方法                  ,则pc寄存器包含当前正在执行的Java虚拟机指令的地址            。如果线程当前正在执行的方法是native的               ,则pc寄存器的值为undefined   。Java虚拟机的pc寄存器足够宽    ,可以容纳特定平台上的returnAddress或native指针                      。

此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域                。

2. Java虚拟机栈

与程序计数器一样                   ,是线程私有的                  ,生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型                  。

「虚拟机栈」里面的每条数据就是「栈帧」,在 Java 方法执行的时候则创建一个「栈帧」并入栈「虚拟机栈」                    。调用结束则「栈帧」出栈   。

每个栈帧包含四个区域:

局部变量表:存储了方法执行过程中需要用到的所有局部变量 操作数栈:暂存变量                ,通过变量的入栈             、出栈等操作来执行计算 动态连接:翻译符号引用为直接引用                     ,即把一个字面量翻译为运行时的一个地址引用 返回地址

每个线程拥有一个「虚拟机栈」    ,每个「虚拟机栈」拥有多个「栈帧」             ,而栈帧则对应着一个方法              。每个「栈帧」包含局部变量表                    、操作数栈        、动态链接          、方法返回地址                     。方法运行结束则意味着该「栈帧」出栈      。

在《Java虚拟机规范》中                    ,对这个内存区域规定了两类异常状况:

如果线程请求的栈深度大于虚拟机所允许的深度        ,将抛出StackOverflowError异常; 如果Java虚拟机栈容量可以动态扩展(HotSpot虚拟机的栈容量不能动态扩展)          ,当栈尝试扩展时无法申请到足够的内存                   ,或为一个新线程初始化JVM栈时没有足够的内存时会抛出OutOfMemoryError异常          。

3. 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的            ,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务       ,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务                      。

《Java虚拟机规范》对本地方法栈中方法使用的语言                   、使用方式与数据结构并没有任何强制规定                  ,因此具体的虚拟机可以根据需要自由实现它               ,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一         。与虚拟机栈一样    ,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常      。

4. Java堆

所有线程共享                   ,虚拟机启动时创建                      。唯一的目的是用于存放对象实例和数组                  ,绝大部分对象实例在堆上分配内存            。

在 Java 中,数组也是对象   。

现代垃圾收集器大部分基于分代收集理论设计                      。“新生代             ”            、“老年代                    ”这些名词仅仅是一部分GC的设计风格                ,而不是《Java虚拟机规范》定义的                。而从G1收集器出现之后                     ,出现了不采用分代设计的新垃圾收集器。

JDK8之后Class对象       、static变量                  、字符串常量池都放在堆里                  。

static变量作为类的信息    ,存储在Class对象里                    。

Java 的对象可以分为基本数据类型和普通对象   。普通对象会在堆上分配              。对于基本数据类型             ,如果是局部变量                    ,则会在栈上分配                     。其他情况        ,通常在在堆上分配          ,逃逸分析的情况下可能会在栈分配      。

如果在Java堆中没有内存完成实例分配                   ,并且堆也无法再扩展时            ,Java虚拟机将会抛出OutOfMemoryError异常          。

4.1 字符串常量池

字符串常量池是由String类维护的一个字符串池                      。是一种池化思想的实现       ,是为了节省重复创建字符串对象的性能开销和内存空间         。

每当代码创建字符串常量时                  ,JVM会首先检查字符串常量池      。如果字符串已经存在池中               ,就返回池中的实例引用                      。如果字符串不在池中    ,就会实例化一个字符串并放到池中            。Java能够进行这样的优化是因为字符串是不可变的                   ,可以不用担心数据冲突进行共享   。

字符串常量池从JDK7开始挪到了堆中                      。

可以通过调用String.intern()方法把一个字符串对象放到字符串常量池中                。如果池中已经存在相等的对象                  ,则会返回已存在对象的引用;否则会把这个字符串对象加入到池中,并返回新加入的字符串对象的引用。

String s = new String("hello")会创建几个对象?

如果字符串常量池中没有"hello"                ,则生成2个                     ,否则只生成一个                  。

String s = new String("abc"); System.out.println((s.intern() == s));打印结果是什么?

打印结果为false                    。s指向的是堆中的对象    ,s.intern()返回的是字符串常量池中的对象的引用   。

4.2 字面量和常量

字面量(literal) :用于表达源码中的一个固定值的符号(notation)              。如整数               、浮点数及字符串等                     。如1    、0x01是整数字面量             ,Hello World是字符串字面量      。

常量:在java中                    ,final修饰的变量也可以被称为是常量          。任何具有不变性的东西都可以称为常量                      。如String对象是常量         。

对象池:是Java语言层面实现的        ,如Integer.valueOf()(Integer i = 10也会调该方法)会使用IntegerCache的缓存对象      。如果使用new Integer(10)则不会使用对象池中的实例                      。

字符串常量池:类似于对象池          ,但它是JVM层面的技术            。字符串常量池的实现是c++实现的StringTable                   ,实际上是一个固定容量的Hashtable            ,每一个bucket包含一系列相同hash码的字符串   。

5. 方法区

用于存储被JVM加载的class的元数据信息       ,比如类的结构                   、运行时的常量池                  、字段、常量                、方法数据                     、方法构造函数以及接口初始化等特殊方法                      。还有JIT编译器编译后的代码缓存等数据                。

JDK8之前                  ,HotSpot采用永久代的概念实现方法区               ,JDK8开始废弃了永久代的概念    ,改用在本地内存(Native Memory)中实现的元空间(Meta-space)来代替。

方法区的GC比较少出现                   ,回收目标主要是针对常量池的回收对类型的卸载                  。

根据《Java虚拟机规范》的规定                  ,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常                    。

5.1 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分   。Class文件中除了有类的版本    、字段             、方法                    、接口等描述信息外                ,还有一项信息是常量池表(Constant Pool Table)                     ,用于存放编译期生成的各种字面量符号引用    ,这部分内容将在类加载后存放到方法区的运行时常量池中              。

一般来说             ,除了保存Class文件中描述的符号引用外                    ,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中                     。

既然运行时常量池是方法区的一部分        ,自然受到方法区内存的限制          ,当常量池无法再申请到内存时会抛出OutOfMemoryError异常      。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
wordpress部署到github(2022年最新wordpress快速搬家图片不显示与修改数据库修改到新域名网站傻瓜教程-电脑学习网)