企业做外贸网站常用术语,黄骅港邮编,佳天下装饰公司怎么样,网站做产品的审核工作目录
1. PC Register#xff08;程序计数器#xff09;
1.1 定义
1.2 工作原理
1.3 特点
1.4 应用
2.虚拟机栈
2.1定义与特性
2.2内存模型
2.3工作原理
2.4异常处理
2.5应用场景
2.6 Slot 复用
2.7 动态链接详解
1. 栈帧与动态链接
动态链接的作用#xff1a…目录
1. PC Register程序计数器
1.1 定义
1.2 工作原理
1.3 特点
1.4 应用
2.虚拟机栈
2.1定义与特性
2.2内存模型
2.3工作原理
2.4异常处理
2.5应用场景
2.6 Slot 复用
2.7 动态链接详解
1. 栈帧与动态链接
动态链接的作用
2. 为什么需要动态链接
符号引用的特性
动态链接 VS 静态链接
3. 动态链接的过程
1从栈帧中获取常量池引用
2解析符号引用
3执行方法调用
4. 动态链接的优化
具体优化策略
3 Native Method Stack本地方法栈
3.1 native 关键字
3.2 定义与特性
3.3 结构与工作原理
3.4 与Java虚拟机栈的区别
3.5 优化与注意事项
4.堆
Java 7及之前
Java 8
Java 9及之后
总结
5.方法区
扩展知识点
1.每个方法所在类都有自己的运行时常量池 注本文参考多位博主作品供大家一起学习进步。 JVM内存结构由五部分组成如下
Method Area方法区Heap堆JVM Stack虚拟机栈PC Register程序计数器Native Method Stacks本地方法栈 1. PC Register程序计数器
1.1 定义
PC Register即程序计数器Program Counter Register是计算机处理器中的一个关键寄存器也被称为指令计数器。它主要用于存放下一条指令所在单元的地址是计算机能够连续执行指令的重要机制之一。以下是关于PC Register的详细概念介绍
基本概念
定义程序计数器是用于存放下一条指令所在单元的地址的地方。当执行一条指令时CPU会根据PC中存放的指令地址将指令由内存取到指令寄存器中此过程称为“取指令”。作用程序计数器是程序控制流的指示器它保证了程序能够按照预定的顺序执行。通过不断更新PC中的地址CPU能够连续地取出并执行指令从而实现程序的连续运行。
1.2 工作原理 取指令当CPU需要执行下一条指令时它会首先查看PC中的地址然后根据这个地址从内存中取出相应的指令。执行指令指令被取出后CPU会对其进行译码和执行。在执行指令的过程中PC中的地址可能会根据指令的类型和需要进行更新。更新PC对于大多数顺序执行的指令PC中的地址会自动加1或加上指令的字节数以指向下一条指令的地址。如果遇到跳转或分支指令PC中的地址会根据指令的要求进行更新以指向新的指令地址。 1.3 特点 线程私有在JVM等环境中每个线程都有自己的程序计数器它是线程私有的。这保证了在多线程环境下每个线程都能够独立地执行自己的程序而不会相互干扰。生命周期程序计数器的生命周期与线程的生命周期保持一致。当线程创建时程序计数器被初始化当线程结束时程序计数器也随之销毁。存储区域程序计数器是一块很小的内存空间几乎可以忽略不计。同时它也是运算速度最快的存储区域之一。 1.4 应用 程序控制程序计数器是实现程序控制流如分支、循环、跳转等的关键机制之一。通过不断更新PC中的地址CPU能够按照预定的程序流程执行指令。异常处理在程序执行过程中如果遇到异常情况如除数为零、数组越界等程序计数器会记录出错时的指令地址以便系统能够定位并处理错误。线程恢复在多线程环境中当线程被中断或挂起后程序计数器会记录线程被中断时的指令地址。当线程恢复执行时CPU会根据程序计数器中的地址继续执行线程的程序。 综上所述PC Register程序计数器是计算机处理器中的一个重要寄存器它通过存放下一条指令的地址来保证程序的连续执行。在程序控制、异常处理和线程恢复等方面都发挥着重要作用。 2.虚拟机栈 虚拟机栈特别是Java虚拟机栈Java Virtual Machine Stack是Java虚拟机中用于描述Java方法执行时内存模型的一个重要组成部分。以下是关于虚拟机栈的详细解释
2.1定义与特性
定义虚拟机栈是线程私有的它的生命周期与线程相同。每个线程在创建时都会创建一个虚拟机栈用于存储该线程执行方法时的各种信息。特性 线程私有每个线程都有自己独立的虚拟机栈互不干扰。生命周期与线程的生命周期一致线程创建时创建线程结束时销毁。存储内容主要存储局部变量表、操作数栈、动态链接、方法出口等信息。
2.2内存模型 栈帧Stack Frame虚拟机栈由多个栈帧组成每个栈帧对应着一次方法调用。当一个方法被调用时就会创建一个新的栈帧并将其压入虚拟机栈的栈顶。当方法执行完毕后对应的栈帧就会从虚拟机栈中弹出并销毁。局部变量表是栈帧中用于存储方法参数和局部变量的一块内存区域。局部变量表中的变量只在当前方法调用中有效方法执行完毕后随着栈帧的销毁而销毁。操作数栈主要用于保存计算过程的中间结果以及作为计算过程中变量临时的存储空间。它是一个后进先出LIFO的栈通过标准的入栈和出栈操作来访问数据。操作数栈的元素可以是任意的Java数据类型。方法刚开始执行时操作数栈是空的在方法执行过程中通过字节码指令对操作数栈进行压栈和出栈的操作。通常进行算数运算的时候是通过操作数栈来进行的又或者是在调用其他方法的时候通过操作数栈进行参数传递。操作数栈可以理解为栈帧中用于计算的临时数据存储区。动态链接每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用用于支持方法调用过程中的动态连接。方法出口包括方法正常退出时的返回地址以及异常退出时的异常处理器信息。 2.3工作原理
方法调用当一个方法被调用时会创建一个新的栈帧并将其压入虚拟机栈的栈顶。然后根据方法的字节码指令执行引擎会操作这个栈帧中的局部变量表和操作数栈完成方法的执行。方法执行在执行过程中如果需要调用其他方法会创建新的栈帧并压入栈顶当前栈帧成为非活动栈帧。当被调用的方法执行完毕后其对应的栈帧会从栈顶弹出之前的栈帧重新成为活动栈帧。方法返回当一个方法执行完毕后会将其返回值如果有的话压入调用者的操作数栈中并弹出当前栈帧。然后调用者的执行引擎会继续执行下一条指令。
2.4异常处理
如果在方法执行过程中遇到异常并且该异常在当前方法内没有得到处理那么会导致当前方法退出并弹出对应的栈帧。同时会根据异常的类型和异常表中的信息找到相应的异常处理器进行处理。程序运行中虚拟机栈可能会出现两种错误 StackOverFlowError若栈的内存大小不允许动态扩展那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候就抛出 StackOverFlowError 错误。
OutOfMemoryError如果栈的内存大小可以动态拓展Classic 虚拟机当虚拟机在动态拓展栈时无法申请到足够的内存空间则抛出 OutOfMemoryError 异常。如果栈的内存大小不可以动态拓展HotSpot 虚拟机线程申请栈空间失败也会出现OutOfMemoryError 异常。 2.5应用场景
虚拟机栈是Java虚拟机中非常重要的一个组成部分它支持着Java程序的运行。在多线程环境下每个线程都有自己独立的虚拟机栈这保证了线程之间的独立性和安全性。同时虚拟机栈也是实现方法调用、参数传递、局部变量存储等功能的关键机制之一。
综上所述虚拟机栈是Java虚拟机中用于描述Java方法执行时内存模型的一个重要组成部分它支持着Java程序的运行和线程之间的独立执行。 局部变量的容量以变量槽Variable Slot为最小单位每个变量槽最大存储32位的数据类型。对于64位的数据类型long、doubleJVM 会为其分配两个连续的变量槽来存储。以下简称 Slot 。 JVM 通过索引定位的方式使用局部变量表索引的范围从0开始至局部变量表中最大的 Slot 数量。普通方法与 static 方法在第 0 个槽位的存储有所不同。非 static 方法的第 0 个槽位存储方法所属对象实例的引用。 2.6 Slot 复用
为了尽可能的节省栈帧空间局部变量表中的 Slot 是可以复用的。方法中定义的局部变量其作用域不一定会覆盖整个方法。当方法运行时如果已经超出了某个变量的作用域即变量失效了那这个变量对应的 Slot 就可以交给其他变量使用也就是所谓的 Slot 复用。通过一个例子来理解变量“失效”。 当虚拟机运行 test 方法就会创建一个栈帧并压入到当前线程的栈中。当运行到 int a 66时在当前栈帧的局部变量中创建一个 Slot 存储变量 a当运行到 int b 55时此时已经超出变量 a 的作用域了变量 a 的作用域在{}所包含的代码块中此时 a 就失效了变量a 占用的 Slot 就可以交给b来使用这就是 Slot 复用。 凡事有利弊。Slot 复用虽然节省了栈帧空间但是会伴随一些额外的副作用。比如Slot 的复用会直接影响到系统的垃圾收集行为。 上段代码很简单先向内存中填充了 64M 的数据然后通知虚拟机进行垃圾回收。为了更清晰的查看垃圾回收的过程我们再虚拟机的运行参数中加上“-verbose:gc”,这个参数的作用就是打印 GC 信息。 打印的GC信息如下 可以看到虚拟机没有回收这 64M 内存。为什么没有被回收其实很好理解当执行 System.gc() 方法时变量 placeholder 还在作用域范围之内虚拟机是不会回收的它还是“有效”的。 我们对上面的代码稍作修改使其作用域“失效”。 当运行到 System.gc() 方法时变量 placeholder 的作用域已经失效了。它已经“无用”了虚拟机会回收它所占用的内存了吧 运行结果 发现虚拟机还是没有回收 placeholder 变量占用的 64M 内存。为什么所想非所见呢在解释之前我们再对代码稍作修改。在System.gc()方法执行之前加入一个局部变量。 在 System.gc() 方法之前加入 int a 0再执行方法查看垃圾回收情况。 发现 placeholder 变量占用的64M内存空间被回收了如果不理解局部变量表的Slot复用很难理解这种现象的。
而 placeholder 变量能否被回收的关键就在于局部变量表中的 Slot 是否还存有关于 placeholder 对象的引用。
第一次修改中限定了 placeholder 的作用域但之后并没有任何对局部变量表的读写操作placeholder 变量在局部变量表中占用的Slot没有被其它变量所复用所以作为 GC Roots 一部分的局部变量表仍然保持着对它的关联。所以 placeholder 变量没有被回收。 第二次修改后运行到 int a 0 时已经超过了 placeholder 变量的作用域此时 placeholder 在局部变量表中占用的Slot可以交给其他变量使用。而变量a正好复用了 placeholder 占用的 Slot至此局部变量表中的 Slot 已经没有 placeholder 的引用了虚拟机就回收了placeholder 占用的 64M 内存空间。 2.7 动态链接详解
1. 栈帧与动态链接
每个 栈帧Stack Frame 都与一个方法调用相对应用于保存方法执行的相关信息。栈帧中的一部分数据结构是 动态链接信息它包含了一个指向 运行时常量池Runtime Constant Pool 的引用。
动态链接的作用
动态链接用于在方法调用时解析常量池中的符号引用从而获得具体的方法或字段的实际地址。 2. 为什么需要动态链接 Java 中的方法调用是通过 符号引用Symbolic Reference来表示的而实际执行需要将这些符号引用解析为具体的 内存地址也称为直接引用。这就是动态链接的主要任务。 符号引用的特性
符号引用 是一种间接的、抽象的标识例如类名、方法名、描述符等。动态链接的过程是将符号引用解析为方法的直接引用这通常涉及到运行时查找和验证。
动态链接 VS 静态链接
静态链接 在编译期确定引用关系如 C/C 的链接器。动态链接 在运行时根据上下文环境解析符号引用使得程序具有更大的灵活性和动态特性。 3. 动态链接的过程
在 JVM 中方法调用的指令例如 invokevirtual、invokestatic 等会触发动态链接。以下是动态链接的主要过程
1从栈帧中获取常量池引用
每个栈帧都持有对所属方法所在类的运行时常量池的引用动态链接通过这个引用查找符号。
2解析符号引用
动态链接会检查运行时常量池中的符号引用并将其解析为
具体的类字段或方法所属的类。方法或字段的实际地址。
3执行方法调用
找到实际地址后JVM 使用该地址执行方法调用。 4. 动态链接的优化
为了提高性能动态链接过程中会利用 缓存机制 或 直接引用避免每次调用都重新解析符号。
具体优化策略 静态方法和私有方法 因为它们在编译期就可以确定调用关系因此使用 静态绑定。 虚方法Virtual Method 使用 虚方法表vtable 来加速方法的动态查找。 内联缓存Inline Cache 在热点代码中缓存方法的直接引用提高调用效率。 3 Native Method Stack本地方法栈
3.1 native 关键字
在 Java 中native 关键字用于声明一个方法为本地方法意味着该方法的实现将在本地代码完成通常是 C 或 C 代码。使用 native关键字可以允许 Java 程序调用本地代码库中的函数从而拓展 Java 的功能并利用已有的本地代码资源。然而使用 native 关键字需要谨慎并注意安全性、性能、兼容性、维护性和资源限制等方面的问题。
3.2 定义与特性
定义本地方法栈是JVM为支持本地方法调用而设置的一个内存区域。本地方法是指使用其他编程语言如C、C等编写的通过JNIJava Native Interface技术与Java代码进行交互的方法。特性 线程私有与Java虚拟机栈类似本地方法栈也是线程私有的每个线程都拥有自己独立的本地方法栈不与其他线程共享。内存管理本地方法栈的大小通常可以通过JVM参数进行设置但具体实现可能因JVM的不同而有所差异。异常处理本地方法栈也可以捕获和处理异常当本地方法抛出异常时JVM会在本地方法栈上找到相应的异常处理器并进行处理。
3.3 结构与工作原理
结构本地方法栈的结构与Java虚拟机栈类似每个栈帧包含了本地方法的相关信息如局部变量表、操作数栈、返回地址等。局部变量表用于存储本地方法的局部变量和参数操作数栈用于执行本地方法中的操作指令。工作原理当Java程序调用本地方法时JVM会在本地方法栈上为该方法创建一个新的栈帧用于保存本地方法的局部变量、参数、返回值和临时数据。在方法的执行过程中JVM会根据需要创建更多的栈帧来支持方法的执行。当方法执行完毕后相应的栈帧会被弹出本地方法栈并释放其所占用的内存资源。
3.4 与Java虚拟机栈的区别
用途Java虚拟机栈用于执行Java方法的调用和返回而本地方法栈则用于执行本地方法的调用和返回。存储内容Java虚拟机栈主要存储Java方法的参数、局部变量和返回值等而本地方法栈则主要存储本地方法的参数、局部变量和返回值等。语言支持Java虚拟机栈中的方法是Java语言编写的而本地方法栈中的方法是使用非Java语言如C、C编写的。
3.5 优化与注意事项
优化本地方法栈为了减少本地方法栈的开销应尽量减少不必要的本地方法调用并合理设置本地方法栈的大小。同时可以使用JIT编译器优化频繁调用的本地方法以提高程序的执行效率。注意事项在编写Java程序时如果涉及到本地方法的调用需要特别注意本地方法栈的大小和异常处理机制以避免栈溢出等问题。此外由于本地方法可能导致Java程序失去平台独立性并增加代码调试和维护的难度因此在选择是否使用本地方法时需要谨慎考虑。
总之本地方法栈是JVM中用于支持本地方法执行的重要组件它通过为本地方法提供独立的内存区域和栈帧结构来支持Java程序与本地代码的交互。了解本地方法栈的工作原理和特性对于编写高效、稳定的Java程序具有重要意义。 4.堆
堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。所有对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法收集堆上对象所占用的内存空间收集的是对象占用的空间而不是对象本身。 Java堆分为年轻代Young Generation和老年代Old Generation年轻代又分为伊甸园Eden和幸存区Survivor区幸存区又分为From Survivor空间和 To Survivor空间。
年轻代存储“新生对象”我们新创建的对象存储在年轻代中。当年轻内存占满后会触发Minor GC清理年轻代内存空间。
老年代存储长期存活的对象和大对象。年轻代中存储的对象经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后会触发Full GC。
注Full GC是清理整个堆空间包括年轻代和老年代。如果Full GC之后堆中仍然无法存储对象就会抛出OutOfMemoryError异常。 Java堆设置常用参数 在Java中不同版本对堆和方法区在Java 8及之后称为“元空间”的存储内容和结构有一些不同。以下是不同Java版本的主要区别
Java 7及之前
堆 (Heap) 存储所有的对象实例以及数组包括类的实例变量。堆分为新生代Young Generation和老年代Old Generation用来管理对象的生命周期。 新生代新创建的对象会分配在新生代中由Eden区和两个Survivor区组成。老年代当对象在新生代中存活一段时间或超过新生代的容量时会被转移到老年代。
方法区 (Method Area) 存储类的元数据Class Metadata、常量池、静态变量和JIT编译后的代码。Java 7及之前使用了**永久代Permanent Generation**来实现方法区。 永久代 (PermGen)存储类信息、静态变量和字符串常量池。字符串常量池在Java 7之前存放于永久代中。
Java 8
堆 (Heap)仍然用于存储对象实例及其实例变量。元空间 (Metaspace) Java 8移除了永久代将方法区改为“元空间”。 元空间存储类元数据并且不再占用堆空间而是直接使用本地内存Native Memory。字符串常量池移动到了堆内存中因此不再受限于永久代的大小。这种改进避免了因为永久代大小不足导致的内存错误如OutOfMemoryError: PermGen space并提高了元数据存储的灵活性。
Java 9及之后
堆 (Heap) 和 元空间 (Metaspace)Java 9及之后的版本仍然遵循Java 8的内存结构。 类数据共享 (Class Data Sharing, CDS)Java 9引入CDS来优化类加载机制允许类的元数据在不同的JVM实例之间共享从而节省内存并加速启动。动态CDS (Dynamic CDS)Java 10进一步扩展了CDS可以动态地生成CDS归档文件。
总结
Java 7及之前使用堆Heap和永久代PermGen。Java 8移除永久代引入元空间Metaspace。Java 9及之后优化CDS机制进一步提升内存使用效率和启动性能。
5.方法区
方法区同 Java 堆一样是被所有线程共享的区间用于存储已被虚拟机加载的类信息、常量、静态变量、即编译器编译后的代码。更具体的说静态变量常量类信息版本、方法、字段等运行时常量池存在方法区中。常量池是方法区的一部分。
当类被加载时类的定义包括类的字节码、类的方法、字段等会被存储在方法区。 注JDK1.8之前方法区称为永恒代并位于堆内存中JDK1.8及以后 使用元空间 MetaSpace 替代方法区元空间并不在 JVM中而是使用本地内存。元空间两个参数 MetaSpaceSize初始化元空间大小控制发生GC阈值
MaxMetaspaceSize 限制元空间大小上限防止异常占用过多物理内存
常量池中存储编译器生成的各种字面量和符号引用。字面量就是Java中常量的意思。比如文本字符串final修饰的常量等。符号引用则包括类和接口的全限定名方法名和描述符字段名和描述符等。 常量池有什么用
优点常量池避免了频繁的创建和销毁对象而影响系统性能其实现了对象的共享。
举个栗子 Integer 常量池缓存池和字符串常量池
Integer常量池
我们知道 基本数据类型比较的是数值而引用数据类型比较的是内存地址。 i1 和 i2 使用 new 关键字每 new 一次都会在堆上创建一个对象所以 i1 i2 为 false。 i3 i4 为什么是 true 呢Integer i3 66 实际上有一步装箱的操作即将 int 型的 66 装箱成 Integer通过 Integer 的 valueOf 方法。 Integer 的 valueOf 方法很简单它判断变量是否在 IntegerCache 的最小值-128和最大值127之间如果在则返回常量池中的内容否则 new 一个 Integer 对象。 而 IntegerCache 是 Integer的静态内部类作用就是将 [-128,127] 之间的数“缓存”在 IntegerCache 类的 cache 数组中valueOf 方法就是调用常量池的 cache 数组不过是将 i3、i4 变量引用指向常量池中没有真正的创建对象。而new Integer(i)则是直接在堆中创建对象。 IntegerCache 类中包含一个构造方法三个静态变量low最小值、high最大值、和Integer数组还有一个静态代码块。静态代码块的作用就是在 IntegerCache 类加载的时候对high最大值以及 Integer 数组初始化。也就是说当 IntegerCache 类加载的时候最大最小值和 Integer 数组就已经初始化好了。这个 Integer 数组其实就是包含了 -128到127之间的所有值。
IntegerCache 源码 而 i5 i6 为 false就是因为 150 不在 Integer 常量池的最大最小值之间【-128,127】从而 new 了一个对象所以为 false。
再看一段拆箱的代码。 由于 i1 和 i2 是 Integer 对象是不能使用运算符的。首先 i1 和 i2 进行自动拆箱操作拆箱成int后再进行数值加法运算。i3 也是拆箱后再与之比较数值是否相等的。所以 i3 i1i2 其实是比较的 int 型数值是否相等所以为true。
String 是由 final 修饰的类是不可以被继承的。通常有两种方式来创建对象。 第一种使用 new 创建的对象存放在堆中。每次调用都会创建一个新的对象。 第二种先在栈上创建一个 String 类的对象引用变量 str然后通过符号引用去字符串常量池中找有没有 “abcd”如果没有则将“abcd”存放到字符串常量池中并将栈上的 str 变量引用指向常量池中的“abcd”。如果常量池中已经有“abcd”了则不会再常量池中创建“abcd”而是直接将 str 引用指向常量池中的“abcd”。 对于 String 类equals 方法用于比较字符串内容是否相同 号用于比较内存地址是否相同即是否指向同一个对象。通过代码验证上面理论。 首先在栈上存放变量引用 str1然后通过符号引用去常量池中找是否有 abcd没有则将 abcd 存储在常量池中然后将 str1 指向常量池的 abcd。当创建 str2 对象去常量池中发现已经有 abcd 了就将 str2 引用直接指向 abcd 。所以str1 str2指向同一个内存地址。 str1 和 str2 使用 new 创建对象分别在堆上创建了不同的对象。两个引用指向堆中两个不同的对象所以为 false。
关于字符串 号连接问题
对于字符串常量的 号连接在程序编译期JVM就会将其优化为 号连接后的值。所以在编译期其字符串常量的值就确定了。 关于字符串引用 号连接问题
对于字符串引用的 号连接问题由于字符串引用在编译期是无法确定下来的在程序的运行期动态分配并创建新的地址存储对象。 对于上边代码str3 等于 str1 引用 字符串常量“b”在编译期无法确定在运行期动态的分配并将连接后的新地址赋给 str3所以 str2 和 str3 引用的内存地址不同所以 str2 str3 结果为 false 通过 jad 反编译工具分析上述代码到底做了什么。编译指令如下 经过 jad 反编译工具反编译代码后代码如下 发现 new 了一个 StringBuilder 对象然后使用 append 方法优化了 操作符。new 在堆上创建对象而 String s1“ab”则是在常量池中创建对象两个应用所指向的内存地址是不同的所以 s1 s2 结果为 false。 注我们已经知道了字符串引用的 号连接问题其实是在运行期间创建一个 StringBuilder 对象使用其 append 方法将字符串连接起来。这个也是我们开发中需要注意的一个问题就是尽量不要在 for 循环中使用 号来操作字符串。看下面一段代码 在 for 循环中使用 连接字符串每循环一次就会新建 StringBuilder 对象append 后就“抛弃”了它。如果我们在循环外创建StringBuilder 对象然后在循环中使用 append 方法追加字符串就可以节省 n-1 次创建和销毁对象的时间。所以在循环中连接字符串一般使用 StringBuilder 或者 StringBuffer而不是使用 号操作。 使用final修饰的字符串 final 修饰的变量是一个常量编译期就能确定其值。所以 str1 b就等同于 a b所以结果是 true。
String对象的intern方法。 通过前面学习我们知道s1s2 实际上在堆上 new 了一个 StringBuilder 对象而 s 在常量池中创建对象 “ab”所以 s3 s 为 false。但是 s3 调用 intern 方法返回的是s3的内容ab在常量池中的地址值。所以 s3.intern() s 结果为 true。 扩展知识点
1.每个方法所在类都有自己的运行时常量池
在 JVM 中每个类包括接口都会有一个 运行时常量池Runtime Constant Pool。运行时常量池是 方法区 中的一部分用来存储与类或接口相关的常量信息包括编译时生成的各种 字面量 和 符号引用。以下是详细说明 1. 每个类的运行时常量池
当一个类被 加载到 JVM 时JVM 会从该类的 class 文件 中提取常量池Constant Pool并将其放入内存中。每个类或接口都有自己独立的运行时常量池用来存储和它相关的信息。
常量池的内容
编译期常量Compile-time Constants 字符串字面量、数字、布尔值等例如 hello, 3.14。符号引用Symbolic References 类的全限定名。字段的名称和描述符。方法的名称和描述符。 2. 运行时常量池的功能
运行时常量池的核心功能是支持动态链接和方法调用。
1符号引用解析
符号引用是对类、字段或方法的一种逻辑描述如名称和描述符。JVM 在运行时会从运行时常量池中查找符号引用并解析为实际的内存地址直接引用。
2动态生成常量
运行时常量池可以在运行时存储新的常量值例如通过 String.intern() 方法动态生成字符串常量。
3支持方法调用
方法调用指令如 invokevirtual, invokestatic需要通过运行时常量池获取目标方法的符号引用并在必要时解析为直接引用。 3. 为什么每个类有独立的运行时常量池 类的独立性 每个类或接口都有独立的常量池因为它们的常量信息是独立的互不干扰。 动态链接需求 不同类加载器加载的类可以有不同的运行时常量池用于支持动态链接和类隔离机制。 内存优化 JVM 通过让每个类独立管理自己的常量池避免不必要的全局资源共享提高效率。 4. 运行时常量池与 Class 文件常量池的关系
Class 文件常量池 编译后的 .class 文件中包含一个常量池表存储的是符号引用和字面量。运行时常量池 当类加载到 JVM 后常量池会被载入运行时常量池供 JVM 使用。在运行时常量池中符号引用可能被解析为直接引用。 5. 常见的运行时常量池操作 动态链接 JVM 从运行时常量池中解析符号引用找到类、字段或方法的具体内存地址。 字符串常量池 字符串字面量如 hello在运行时常量池中存储并可能被转移到 JVM 的字符串池String Pool。 异常处理 异常表中可能包含符号引用用于定位异常处理的目标类或方法。 6. 示例
假设一个简单的类
在 Example 类的运行时常量池中可能包含
类名 Example。方法名和描述符sayHello 和 ()V无参数无返回值。System 和 out 的符号引用。字符串字面量 Hello, World!。
当 JVM 运行 sayHello 方法时会从运行时常量池中查找这些信息并完成解析。 7. 总结
每个类确实有独立的运行时常量池。它主要用于符号引用解析、动态链接和常量存储。JVM 的设计确保了常量池的独立性便于类的动态加载和隔离运行。