JVM体系结构
目录
(1). 程序计数器(Program Counter Register, PC Register)
(2). Java 虚拟机栈(Java Virtual Machine Stacks, JVM Stack)
(5). 运行时常量池(Run-Time Constant Pool)
(6). 本地方法栈(Native Method Stacks)
3. 本地方法接口(Native Method Interface, JNI)
4. 本地方法库(Native Method Library)
七.HotSpot(Oracle JDK/Open JDK内部使用的JVM就是HotSpot)
方法区(永久代,Permanent Generation):
运行时常量池(Runtime Constant Pool):
(1). 符号引用(Symbolic References)
(4). 运行时常量池(Run-Time Constant Pool)
JVM对应了一套规范(Java虚拟机规范),它可以有不同的实现
①.JVM规范是一种抽象的概念,它可以有多种不同的实现。例如:
1.HotSpot:HotSpot 由 Oracle 公司开发,是目前最常用的虚拟机实现,也是默认的 Java 虚拟机,默认包含在 Oracle JDK 和 OpenJDK 中
2.JRockit:JRockit 也是由 Oracle 公司开发。它是一款针对生产环境优化的 JVM 实现,能够提供高性能和可伸缩性
3.IBM JDK:IBM JDK 是 IBM 公司开发的 Java 环境,采用了与 HotSpot 不同的 J9 VM,能够提供更小的内存占用和更迅速的启动时间
4.Azul Zing:Azul Zing 是针对生产环境优化的虚拟机实现,能够提供高性能和实时处理能力,适合于高负载的企业应用和实时分析等场景
5.OpenJ9:OpenJ9 是由 IBM 开发的优化的 Java 虚拟机实现,支持高度轻量级、低时延的 GC、优化的 JIT 编译器和用于健康度测试的可观察性仪表板
②.下图是从oracle官网上截取的Java虚拟机规范中的一部分。(大家也可以找一下oracle官方文档)
③. 我们主要研究运行时数据区。运行时数据区包括6部分:
1.The pc Register(程序计数器)
2.Java Virtual Machine Stacks(Java虚拟机栈)
3.Heap(堆)
4.Method Area(方法区)
5.Run-Time Constant Pool(运行时常量池)
6.Native Method Stacks(本地方法栈)
Java 虚拟机(JVM)规范 及其不同实现的概述。JVM 规范定义了一套标准,而具体的 JVM 实现可以根据这些标准进行不同的优化和扩展。以下是更详细的解释和补充:
一. JVM 规范
JVM 规范就像是一份蓝图或者标准。它定义了 Java 虚拟机应该具备的功能、特性、运行时环境等诸多方面的要求。不同的公司或者组织可以根据这个规范来实现自己的 Java 虚拟机。这就好比建筑规范规定了一栋房子应该有的结构、功能等基本要求,而不同的建筑商可以根据这个规范建造出不同风格、不同用途的房子。
-
定义:JVM 规范是 Java 平台的核心部分,定义了 Java 虚拟机的行为、类文件格式、字节码指令集、内存模型等。
-
抽象性:JVM 规范是一种抽象的概念,不依赖于具体的实现。只要符合规范,任何组织或个人都可以开发自己的 JVM 实现。
-
跨平台性:JVM 规范确保了 Java 程序的“一次编写,到处运行”(Write Once, Run Anywhere)特性。
二. JVM 实现
JVM 规范有多种不同的实现,每种实现都有其独特的特点和优化目标。以下是一些常见的 JVM 实现:
(1) HotSpot
开发者与包含情况:HotSpot 是由 Oracle 公司开发的。它在 Java 开发中占据非常重要的地位,因为它是默认的 Java 虚拟机,被包含在 Oracle JDK 和 OpenJDK 中。这意味着当大多数开发者使用 Oracle JDK 或者 OpenJDK 进行 Java 编程时,默认使用的就是 HotSpot 虚拟机。
应用场景与优势:由于其默认性和广泛使用,它在各种 Java 应用场景中都有涉及。无论是小型的命令行工具开发,还是大型的企业级应用系统,HotSpot 都能发挥作用。而且它经过多年的优化和发展,在性能和稳定性方面都有很好的表现。
开发者:Oracle(最初由 Sun Microsystems 开发)。
特点:
目前最常用的 JVM 实现,默认包含在 Oracle JDK 和 OpenJDK 中。
提供了高性能的即时编译器(JIT)和垃圾回收器(GC)。
支持多种垃圾回收算法,如 G1、ZGC、Shenandoah 等。
适用场景:通用场景,适合大多数 Java 应用程序。
(2) JRockit
开发者与特点:同样是 Oracle 公司开发的 JRockit 虚拟机,主要是针对生产环境进行优化。在企业级的生产环境中,对系统的性能和可伸缩性要求很高。JRockit 能够满足这些要求,它可以在高负载的情况下,很好地利用系统资源,提供高性能的服务,并且能够根据业务需求灵活地伸缩,比如当业务量突然增大时,可以有效地扩展资源利用来应对。
开发者:Oracle(最初由 BEA Systems 开发)。
特点:
针对生产环境优化,提供高性能和可伸缩性。
专注于低延迟和高吞吐量。
支持实时垃圾回收(Real-Time GC)。
现状:JRockit 已与 HotSpot 合并,其部分特性被整合到 HotSpot 中。
(3) IBM JDK(J9 VM)
开发者与优势:IBM JDK 是 IBM 公司开发的 Java 环境,其中采用的 J9 VM 有自己的特色。它在内存占用方面表现出色,能够用较小的内存运行 Java 程序。对于一些内存资源有限的环境,比如在嵌入式系统或者资源受限的服务器环境中,这是一个很大的优势。而且它的启动时间更迅速,在需要快速启动 Java 应用的场景下,如一些对响应时间要求极高的短任务应用场景,J9 VM 可以更快地让程序运行起来。
开发者:IBM。
特点:
采用 J9 虚拟机,内存占用小,启动速度快。
针对 IBM 硬件和操作系统进行了优化。
支持高度可配置的垃圾回收策略。
适用场景:企业级应用,尤其是运行在 IBM 硬件上的应用。
(4) Azul Zing
应用场景与优势:Azul Zing 是针对生产环境优化的虚拟机实现。它在高性能和实时处理方面表现卓越。在企业应用中,当面临高负载的情况,比如大量用户同时访问一个电商网站的促销活动页面,或者对数据进行实时分析,如金融交易数据的实时监控和分析,Azul Zing 能够很好地处理这些复杂的任务,保证系统的高效运行。
开发者:Azul Systems。
特点:
针对生产环境优化,提供高性能和实时处理能力。
使用 C4(Continuously Concurrent Compacting Collector)垃圾回收器,实现低延迟和高吞吐量。
支持大规模内存和多核处理器。
适用场景:高负载的企业应用、实时分析和低延迟系统。
(5) OpenJ9
开发者与特点:OpenJ9 是由 IBM 开发的优化的 Java 虚拟机实现。它支持高度轻量级的特性,对于资源的利用比较高效。在垃圾回收(GC)方面,它采用了低时延的 GC 机制,这意味着在回收垃圾对象时,对程序运行的暂停时间较短,能够减少对程序性能的影响。其优化的 JIT(即时编译)编译器可以提高代码的执行效率,并且可观察性仪表板可以用于健康度测试,方便开发者和运维人员监控虚拟机的运行状态,及时发现和解决问题。
开发者:IBM(开源版本)。
特点:
基于 IBM J9 虚拟机,支持高度轻量级和低时延的垃圾回收。
优化的 JIT 编译器,提供高性能。
提供可观察性工具,用于健康度测试和性能监控。
适用场景:云原生应用、微服务和容器化环境。
三. JVM 实现的选择
选择 JVM 实现时,需要考虑以下因素:
-
性能需求:不同的 JVM 实现在性能(如吞吐量、延迟)方面有不同的优化。
-
硬件和操作系统:某些 JVM 实现针对特定的硬件或操作系统进行了优化。
-
垃圾回收策略:不同的 JVM 实现支持不同的垃圾回收算法,适用于不同的应用场景。
-
社区和支持:HotSpot 和 OpenJDK 拥有广泛的社区支持,而 Azul Zing 和 IBM JDK 提供商业支持。
四. JVM 的核心组件
无论哪种 JVM 实现,都包含以下核心组件:
-
类加载器(Class Loader):负责加载类文件。
-
执行引擎(Execution Engine):解释或编译字节码并执行。
-
垃圾回收器(Garbage Collector):管理内存,回收不再使用的对象。
-
运行时数据区(Runtime Data Areas):包括方法区、堆、栈、程序计数器等。
五.JVM总结
-
JVM 规范 是 Java 平台的核心,定义了虚拟机的行为标准。
-
JVM 实现 有多种,如 HotSpot、JRockit、IBM JDK、Azul Zing 和 OpenJ9,每种实现都有其独特的特点和优化目标。
-
选择合适的 JVM 实现需要根据具体的应用场景、性能需求和硬件环境进行权衡。
六.Java 虚拟机(JVM)架构概述
JVM体系结构图(该图属于JVM规范,不是具体的实现)
1.Java 虚拟机(JVM)运行时数据区
你列出的内容是 Java 虚拟机(JVM)运行时数据区 的六个核心部分。这些区域是 JVM 内存模型的核心组成部分,用于存储程序运行时的数据。以下是每个部分的详细解释:
(1). 程序计数器(Program Counter Register, PC Register)
定义与功能:程序计数器是一块较小的内存空间。它可以看作是当前线程所执行的字节码的行号指示器。在 Java 虚拟机的多线程环境中,每个线程都有自己独立的程序计数器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,从而实现代码的顺序执行、分支、循环、跳转等操作。
作用示例:例如,当一个方法中有一个循环结构时,程序计数器会不断地更新,以指向循环体中的下一条字节码指令,直到循环条件不满足为止。而且在方法调用或者返回时,程序计数器的值也会相应地改变,以确保程序能够正确地从调用后的位置继续执行或者返回到调用点。
特点:它是线程私有的,这是为了保证各个线程能够独立地记录自己的执行位置,不受其他线程的干扰。并且,它是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError(内存溢出异常)情况的区域,因为它的内存占用非常小,主要就是记录一个地址。
作用:
记录当前线程执行的字节码指令地址。
如果当前执行的是 Java 方法,PC 寄存器存储的是正在执行的指令地址。
如果当前执行的是本地方法(Native Method),PC 寄存器的值为空(Undefined)。
特点:
每个线程独享一个程序计数器。
是线程私有的内存区域。
不会抛出
OutOfMemoryError
异常。
(2). Java 虚拟机栈(Java Virtual Machine Stacks, JVM Stack)
定义与结构:Java 虚拟机栈也是线程私有的。它的生命周期与线程相同。它描述的是 Java 方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
栈帧详细内容:
● 局部变量表:用于存放方法参数和方法内部定义的局部变量。例如,在一个简单的加法方法int add(int a, int b)中,a和b这两个参数就会存储在局部变量表中。
● 操作数栈:在方法执行过程中,字节码指令会向操作数栈中压入(push)和弹出(pop)数据,用于进行运算。比如在计算a + b时,先将a和b压入操作数栈,然后执行加法指令,从操作数栈中取出a和b进行相加,结果再放回操作数栈。
● 动态连接:它用于支持方法调用过程中的动态连接。在 Java 中,存在虚方法(如被重写的方法)的调用,动态连接可以在运行时确定具体要调用的方法版本。
● 方法出口:当一个方法执行完毕后,需要通过方法出口信息返回到调用该方法的位置。
异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,就会抛出 Stack - OverflowError(栈溢出异常);如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError。
作用:
存储方法调用的栈帧(Stack Frame)。
每个方法调用时,会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法返回地址。
特点:
每个线程独享一个虚拟机栈。
栈帧随着方法的调用而创建,随着方法的结束而销毁。
可能抛出
StackOverflowError
(栈深度超过限制)或OutOfMemoryError
(无法分配更多栈空间)。
(3). 堆(Heap)
定义与用途:堆是 Java 虚拟机所管理的内存中最大的一块。它被所有线程共享,主要用于存放对象实例。在 Java 中,通过new关键字创建的对象都会在堆中分配内存。例如,Object obj = new Object();这个obj对象就存储在堆中。
内存分配与回收:堆内存的分配和回收是 Java 虚拟机中一个复杂的过程。垃圾收集器(Garbage Collector)主要就是负责回收堆中不再被使用的对象内存。垃圾收集算法有多种,如标记 - 清除算法、复制算法、标记 - 整理算法等。这些算法会根据对象的存活状态来确定哪些对象需要被回收。
内存分区与优化:堆通常可以分为新生代(Young Generation)和老年代(Old Generation)。新生代又可以细分为 Eden 区和两个 Survivor 区。这种分区是为了更高效地进行垃圾回收。例如,大部分对象在创建后很快就会死亡,所以在新生代中采用复制算法可以快速地回收这些短生命周期的对象,而老年代中的对象通常存活时间较长,采用标记 - 整理算法等更合适。
作用:
存储对象实例和数组。
是垃圾回收器管理的主要区域。
特点:
所有线程共享。
分为新生代(Young Generation)和老年代(Old Generation)。
新生代进一步分为 Eden 区、Survivor 区(From 和 To)。
可能抛出
OutOfMemoryError
(堆内存不足)。
(4). 方法区(Method Area)
定义与存储内容:方法区也是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。例如,一个类的字节码文件被加载到虚拟机中后,类的结构信息(如类名、方法签名、字段等)就存储在方法区。
与运行时常量池的关系:方法区中有一个重要的组成部分是运行时常量池。运行时常量池主要用于存放编译期生成的各种字面量和符号引用。当类加载时,这些内容会被放入运行时常量池中。比如一个字符串常量"Hello World",在类加载时会被放入运行时常量池。
内存回收与变化:方法区的内存回收相对堆来说比较不频繁。主要是回收常量池中废弃的常量和不再使用的类型信息。在 Java 8 以前,方法区也被称为永久代(Permanent Generation),Java 8 之后,移除了永久代,取而代之的是元空间(Metaspace),元空间使用本地内存,而不是虚拟机内存,这样可以避免永久代内存溢出的一些问题。
作用:
存储类的元数据(如类名、方法信息、字段信息、常量池等)。
在 Java 8 及以后,方法区的实现是 Metaspace(元空间),使用本地内存(直接内存)。
特点:
所有线程共享。
存储静态变量、常量、类信息等。
可能抛出
OutOfMemoryError
(Metaspace 内存不足)。
(5). 运行时常量池(Run-Time Constant Pool)
定义与来源:运行时常量池是方法区的一部分。它在类加载后存放的是编译期生成的各种字面量和符号引用。字面量包括文本字符串、被声明为 final 的常量值等。例如,final int MAX_VALUE = 100;这个MAX_VALUE的常量值会在运行时常量池中。符号引用包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符等。
动态性:运行时常量池具有一定的动态性。在运行期间,也可以将新的常量放入池中。比如通过String.intern()方法可以将字符串放入运行时常量池。如果池中已经存在相同的字符串,就返回池中的引用;如果不存在,就将新的字符串放入池中并返回引用。
作用:
存储类文件中的常量池(Constant Pool)信息,如字符串常量、类和接口的全限定名、字段和方法的名称和描述符等。
是方法区的一部分。
特点:
所有线程共享。
动态性:运行时常量池可以在运行时添加新的常量。
可能抛出
OutOfMemoryError
(常量池内存不足)。
(6). 本地方法栈(Native Method Stacks)
定义与用途:本地方法栈与 Java 虚拟机栈类似,但是它是为本地(Native)方法服务的。本地方法是指用非 Java 语言(如 C 或 C++)编写的方法,这些方法可以通过 Java Native Interface(JNI)来调用。当一个本地方法被调用时,会在本地方法栈中创建一个栈帧,用于存储本地方法的局部变量、操作数栈等信息。
异常情况:本地方法栈也会出现 Stack - OverflowError 和 OutOfMemoryError 这两种异常情况,与 Java 虚拟机栈类似,当栈深度超过限制或者无法申请到足够的内存时就会抛出相应的异常。
作用:
支持本地方法(Native Method)的执行。
本地方法是用其他语言(如 C、C++)编写的方法。
特点:
每个线程独享一个本地方法栈。
类似于虚拟机栈,但用于本地方法。
可能抛出
StackOverflowError
或OutOfMemoryError
。
(7). 运行时数据区的关系
-
线程共享:
-
方法区、堆和运行时常量池是所有线程共享的。
-
-
线程独享:
-
程序计数器、虚拟机栈和本地方法栈是每个线程独享的。
-
-
垃圾回收:
-
堆是垃圾回收的主要区域,方法区(Metaspace)也会被垃圾回收。
-
(8). 总结
-
程序计数器:记录当前线程执行的字节码指令地址。
-
虚拟机栈:存储方法调用的栈帧。
-
堆:存储对象实例和数组,是垃圾回收的主要区域。
-
方法区:存储类的元数据,Java 8 后由 Metaspace 实现。
-
运行时常量池:存储类文件中的常量池信息。
-
本地方法栈:支持本地方法的执行。
这些运行时数据区共同构成了 JVM 的内存模型,是 Java 程序运行的基础。
2. 执行引擎(Execution Engine)
-
作用:负责执行字节码指令。
-
主要组件:
-
解释器(Interpreter):逐行解释执行字节码。
-
即时编译器(JIT Compiler):将热点代码(频繁执行的代码)编译为本地机器代码,以提高执行效率。
-
垃圾回收器(Garbage Collector):管理堆内存,回收不再使用的对象。
-
3. 本地方法接口(Native Method Interface, JNI)
-
作用:提供 Java 代码与本地代码(如 C、C++)交互的接口。
-
特点:
-
允许 Java 程序调用本地方法。
-
本地方法是用其他语言编写的方法,通常用于与操作系统或硬件交互。
-
4. 本地方法库(Native Method Library)
-
作用:包含本地方法的实现。
-
特点:
-
本地方法库通常是用 C 或 C++ 编写的动态链接库(如
.dll
或.so
文件)。 -
通过 JNI 与 Java 代码交互。
-
5. 线程共享与线程私有
-
线程共享:
-
堆、方法区和运行时常量池是所有线程共享的。
-
-
线程私有:
-
程序计数器、虚拟机栈和本地方法栈是每个线程独享的。
-
七.HotSpot(Oracle JDK/Open JDK内部使用的JVM就是HotSpot)
1.JDK6的HotSpot
(1).JDK 6 中的 HotSpot 内存结构
在 JDK 6 中,HotSpot JVM 的内存结构主要包括以下几个部分:
堆(Heap):
年轻代(Young Generation):这是堆的一部分,刚通过new关键字创建出来的对象会首先被分配到年轻代。年轻代又可以进一步细分为 Eden 区和两个 Survivor 区(图中未详细展示)。由于新创建的对象通常生命周期较短,大部分对象在年轻代中经过几次垃圾回收后就会被回收掉,只有少数存活下来的对象会被转移到老年代。例如,在一个简单的 Java 程序中,不断创建临时对象(如循环中创建的临时变量对象),这些对象一般会先在年轻代中。
老年代(Old Generation):经过垃圾回收之后仍然存活的对象会从年轻代转移到老年代。老年代中的对象相对来说生命周期较长,垃圾回收的频率较低。比如一些全局的配置对象、缓存对象等,可能会在程序运行的较长时间内一直存活,就会存储在老年代。
①年轻代(Young Generation)
作用:存储刚创建的对象。
特点:
分为 Eden 区和两个 Survivor 区(From 和 To)。
新创建的对象首先分配在 Eden 区。
经过一次垃圾回收后,存活的对象会被移动到 Survivor 区。
经过多次垃圾回收后,仍然存活的对象会被提升到老年代。
② 老年代(Old Generation)
作用:存储经过多次垃圾回收后仍然存活的对象。
特点:
老年代的对象生命周期较长。
老年代的垃圾回收频率较低,但每次回收的时间较长。
方法区(永久代,Permanent Generation):
存储内容:方法区中存储了 Classes(类的信息)、类的静态变量、字符串常量池等内容。类的信息包括类的结构、方法、字段等定义;类的静态变量是属于类级别的变量,在整个程序运行期间只有一份,被所有该类的实例共享;字符串常量池用于存储字符串常量,以提高字符串的使用效率和节省内存空间。例如,定义一个类
public class MyClass
{ static int staticVar = 10;
}
其中的staticVar就存储在方法区的类的静态变量区域,而"Hello"这样的字符串常量可能会存储在字符串常量池中。
与堆的关系:在 JDK6 的 HotSpot 中,永久代和堆是相邻的,它们使用连续的物理内存,但内存空间是隔离的。这意味着虽然它们在物理上是连续的,但在逻辑上是相互独立的区域,各自有自己的管理和分配机制。
垃圾收集:永久代的垃圾收集是和老年代捆绑在一起的。这是因为在 JDK6 中,永久代和老年代的垃圾回收机制有一定的关联。当永久代或老年代中的任何一个区域满了,都会触发对永久代和老年代的垃圾收集操作。例如,如果程序中加载了大量的类,导致永久代空间不足,或者老年代中对象积累过多,都可能引发垃圾回收,以释放内存空间,保证程序的正常运行。
运行时常量池(Runtime Constant Pool):
存储内容:运行时常量池主要存储符号引用(如类全名、字段全名、方法全名等)、字面量(如字符串常量、基本类型常量等)以及其他相关信息。这些信息在类加载后被放入运行时常量池,供程序运行时使用。例如,在代码中调用一个方法myMethod(),在编译后的字节码中,对myMethod的引用就是以符号引用的形式存储在运行时常量池中,在运行时再通过动态链接等机制将其转换为实际的方法调用地址。
③永久代(Permanent Generation)
作用:存储类的元数据(如类名、方法信息、字段信息、常量池等)。
特点:
永久代和堆是相邻的,使用连续的物理内存,但内存空间是隔离的。
永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。
在 JDK 8 及以后,永久代被 Metaspace(元空间) 取代,使用本地内存(直接内存)。
(2). 符号引用
-
定义:符号引用是一种逻辑引用,用于描述类、字段和方法的全名。
-
作用:
-
在类加载过程中,符号引用会被解析为直接引用。
-
符号引用包括类全名、字段全名、方法全名等。
-
-
示例:
-
类全名:
java/lang/String
-
字段全名:
java/lang/String.value
-
方法全名:
java/lang/String.length()I
-
(3). 垃圾回收机制
在 JDK 6 中,HotSpot JVM 的垃圾回收机制主要包括以下几种:
① 年轻代垃圾回收(Minor GC)
作用:回收年轻代中的对象。
特点:
频率较高,但每次回收的时间较短。
使用复制算法(Copying Algorithm),将存活的对象从 Eden 区和 Survivor 区复制到另一个 Survivor 区。
② 老年代垃圾回收(Major GC/Full GC)
作用:回收老年代中的对象。
特点:
频率较低,但每次回收的时间较长。
使用标记-清除算法(Mark-Sweep Algorithm)或标记-整理算法(Mark-Compact Algorithm)。
③ 永久代垃圾回收
作用:回收永久代中的类元数据。
特点:
永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。
(4). JDK 6 到 JDK 8 的变化
-
永久代的移除:
-
在 JDK 8 及以后,永久代被 Metaspace(元空间) 取代。
-
Metaspace 使用本地内存(直接内存),不再与堆内存相邻。
-
Metaspace 的垃圾回收与堆内存的垃圾回收是分离的。
-
(5). 总结
-
年轻代:存储新创建的对象,使用复制算法进行垃圾回收。
-
老年代:存储长期存活的对象,使用标记-清除或标记-整理算法进行垃圾回收。
-
永久代:存储类的元数据,垃圾回收与老年代捆绑在一起。
-
符号引用:用于描述类、字段和方法的全名,在类加载过程中被解析为直接引用。
2.JDK7的HotSpot
JVM规范的实现:HotSpot(Oracle JDK/Open JDK内部使用的JVM就是HotSpot)
JDK7的HotSpot,这是一个过渡的版本,该版本相对于JDK6来说,变化如下:
1.类的静态变量转移到堆中了
2.字符串常量池转移到堆中了
3.运行时常量池中的符号引用转移到本地内存了
在 JDK 7 中,HotSpot JVM 进行了一些重要的内存结构调整,这些变化标志着 JVM 内存模型的逐步优化和改进。以下是 JDK 7 相对于 JDK 6 的主要变化及其详细解释:
(1). 类的静态变量转移到堆中
-
JDK 6 及以前:
-
类的静态变量(Static Variables)存储在 永久代(Permanent Generation) 中。
-
-
JDK 7 的变化:
-
类的静态变量被移动到 堆(Heap) 中。
-
-
原因:
-
永久代的大小是固定的,容易导致
OutOfMemoryError
。 -
将静态变量移动到堆中,可以利用堆的动态扩展能力,减少内存溢出的风险。
-
-
影响:
-
静态变量的生命周期与对象实例相同,由垃圾回收器管理。
-
(2). 字符串常量池转移到堆中
-
JDK 6 及以前:
-
字符串常量池(String Pool)存储在 永久代(Permanent Generation) 中。
-
-
JDK 7 的变化:
-
字符串常量池被移动到 堆(Heap) 中。
-
-
原因:
-
永久代的大小有限,而字符串常量池可能占用大量内存。
-
将字符串常量池移动到堆中,可以利用堆的动态扩展能力,减少内存溢出的风险。
-
-
影响:
-
字符串常量池中的字符串对象由垃圾回收器管理,不再受永久代大小的限制。
-
(3). 运行时常量池中的符号引用转移到本地内存
-
JDK 6 及以前:
-
运行时常量池(Run-Time Constant Pool)中的符号引用(Symbolic References)存储在 永久代(Permanent Generation) 中。
-
-
JDK 7 的变化:
-
运行时常量池中的符号引用被移动到 本地内存(Native Memory) 中。
-
-
原因:
-
符号引用占用的内存较大,且与类的元数据关系密切。
-
将符号引用移动到本地内存,可以减少永久代的内存压力。
-
-
影响:
-
符号引用的管理更加灵活,不再受永久代大小的限制。
-
(4). JDK 7 的其他变化
-
永久代的逐步淘汰:
-
JDK 7 是永久代向元空间(Metaspace)过渡的版本。
-
虽然永久代仍然存在,但部分内容(如静态变量、字符串常量池)已经被移动到堆中。
-
-
垃圾回收的改进:
-
JDK 7 引入了 G1 垃圾回收器(Garbage-First Collector),作为实验性功能。
-
G1 垃圾回收器旨在替代传统的 CMS(Concurrent Mark-Sweep)回收器,提供更可预测的停顿时间。
-
(5). JDK 7 的内存结构总结
-
堆(Heap):
-
存储对象实例、数组、类的静态变量和字符串常量池。
-
-
本地内存(Native Memory):
-
存储运行时常量池中的符号引用。
-
-
永久代(Permanent Generation):
-
仍然存在,但部分内容被移动到堆和本地内存中。
-
(6). 总结
-
JDK 7 的变化:
-
类的静态变量和字符串常量池被移动到堆中。
-
运行时常量池中的符号引用被移动到本地内存。
-
这些变化减少了永久代的内存压力,提高了 JVM 的稳定性和性能。
-
-
JDK 7 的过渡性:
-
JDK 7 是永久代向元空间过渡的版本,为 JDK 8 的彻底移除永久代奠定了基础。
-
3.JDK8及更高版本的HotSpot
▼
JVM规范的实现:HotSpot(Oracle JDK/Open JDK内部使用的JVM就是HotSpot)
以下是JDK8及更高版本的HotSpot,相对于JDK7来说发生了如下变化:
1.彻底删除永久代(为了避免OOM错误的发生)
2.将方法区的实现转移到本地内存
3.将符号引用重新放回运行时常量池
在 JDK 8 及更高版本中,HotSpot JVM 进行了进一步的内存结构调整,彻底移除了 永久代(Permanent Generation),并将方法区的实现转移到 本地内存(Native Memory) 中。以下是这些变化的详细解释:
(1). 彻底删除永久代
-
JDK 7 及以前:
-
永久代用于存储类的元数据(如类名、方法信息、字段信息、常量池等)。
-
永久代的大小是固定的,容易导致
OutOfMemoryError
(OOM)。
-
-
JDK 8 的变化:
-
永久代被彻底移除。
-
类的元数据现在存储在 元空间(Metaspace) 中。
-
-
原因:
-
永久代的大小有限,且难以动态调整,容易导致内存溢出。
-
元空间使用本地内存(直接内存),可以根据需要动态扩展,减少了 OOM 的风险。
-
-
影响:
-
不再需要手动调整永久代的大小(如
-XX:PermSize
和-XX:MaxPermSize
)。 -
元空间的大小由本地内存限制,默认情况下没有上限(受操作系统和物理内存限制)。
-
(2). 将方法区的实现转移到本地内存
-
JDK 7 及以前:
-
方法区(Method Area)的实现是永久代,存储在 JVM 的堆内存中。
-
-
JDK 8 的变化:
-
方法区的实现转移到 本地内存(Native Memory) 中,称为 元空间(Metaspace)。
-
-
特点:
-
元空间使用本地内存,而不是 JVM 的堆内存。
-
元空间的大小可以动态调整,默认情况下没有上限。
-
元空间的垃圾回收与堆内存的垃圾回收是分离的。
-
-
配置参数:
-
-XX:MetaspaceSize
:初始元空间大小。 -
-XX:MaxMetaspaceSize
:最大元空间大小(默认无限制)。
-
(3). 将符号引用重新放回运行时常量池
-
JDK 7 的变化:
-
运行时常量池(Run-Time Constant Pool)中的符号引用(Symbolic References)被移动到本地内存。
-
-
JDK 8 的变化:
-
符号引用重新放回 运行时常量池,运行时常量池仍然存储在元空间中。
-
-
原因:
-
符号引用与类的元数据关系密切,将其放回运行时常量池可以简化内存管理。
-
-
影响:
-
运行时常量池仍然存储在元空间中,但符号引用的管理更加集中。
-
(4). JDK 8 的内存结构总结
-
堆(Heap):
-
存储对象实例、数组、类的静态变量和字符串常量池。
-
-
元空间(Metaspace):
-
存储类的元数据(如类名、方法信息、字段信息、常量池等)。
-
使用本地内存(直接内存),大小可以动态调整。
-
-
运行时常量池(Run-Time Constant Pool):
-
存储在元空间中,包含符号引用。
-
(5). JDK 8 的其他改进
-
垃圾回收器的改进:
-
G1 垃圾回收器(Garbage-First Collector)成为默认的垃圾回收器。
-
G1 旨在提供更可预测的停顿时间,适用于大内存和多核处理器的环境。
-
-
Lambda 表达式和 Stream API:
-
JDK 8 引入了 Lambda 表达式和 Stream API,提高了代码的简洁性和可读性。
-
(6). 总结
-
JDK 8 的变化:
-
彻底移除了永久代,避免了 OOM 错误。
-
将方法区的实现转移到本地内存,称为元空间。
-
将符号引用重新放回运行时常量池。
-
-
优点:
-
元空间使用本地内存,大小可以动态调整,减少了内存溢出的风险。
-
简化了内存管理,提高了 JVM 的稳定性和性能。
-
4.符号引用和字面量
在 Java 中,符号引用(Symbolic References) 和 字面量(Literals) 是两个重要的概念,分别用于描述类、字段、方法的引用和常量值。以下是它们的详细解释和区别:
(1). 符号引用(Symbolic References)
定义
符号引用是一种逻辑引用,用于描述类、字段和方法的全名。它是一种抽象的引用形式,不直接指向内存地址,而是在类加载过程中被解析为直接引用。
作用
-
在类加载过程中,符号引用会被解析为直接引用。
-
符号引用包括类全名、字段全名、方法全名等。
示例
-
类全名:
java/lang/String
-
字段全名:
java/lang/String.value
-
方法全名:
java/lang/String.length()I
特点
-
逻辑性:符号引用是一种逻辑描述,不直接指向内存地址。
-
动态解析:在类加载过程中,符号引用会被解析为直接引用。
-
存储位置:符号引用存储在运行时常量池(Run-Time Constant Pool)中。
(2). 字面量(Literals)
定义
字面量是源代码中直接表示常量值的符号。它们是程序中直接写出的固定值,不需要计算或解析。
作用
-
用于表示常量值,如整数、浮点数、字符、字符串等。
-
字面量在编译时被确定,并存储在运行时常量池中。
示例
-
整数字面量:
10
,0xFF
-
浮点数字面量:
3.14
,2.0e-6
-
字符字面量:
'A'
,'\n'
-
字符串字面量:
"Hello, World!"
-
布尔字面量:
true
,false
-
空字面量:
null
特点
-
直接性:字面量是直接写出的常量值,不需要计算。
-
存储位置:字面量存储在运行时常量池中。
-
不可变性:字面量的值在编译时确定,不可更改。
(3). 符号引用和字面量的区别
特性 | 符号引用 | 字面量 |
---|---|---|
定义 | 逻辑引用,描述类、字段、方法的全名 | 直接表示常量值的符号 |
作用 | 在类加载过程中被解析为直接引用 | 表示常量值,如整数、字符串等 |
存储位置 | 运行时常量池 | 运行时常量池 |
示例 | java/lang/String , java/lang/String.length()I | 10 , 3.14 , "Hello" , true |
解析时机 | 类加载时解析 | 编译时确定 |
(4). 运行时常量池(Run-Time Constant Pool)
-
作用:
-
存储类文件中的常量池信息,包括符号引用和字面量。
-
是方法区的一部分。
-
-
特点:
-
所有线程共享。
-
动态性:运行时常量池可以在运行时添加新的常量。
-
(5). 示例代码
以下是一个简单的 Java 程序,展示了符号引用和字面量的使用:
public class Example {
public static void main(String[] args) {
// 字面量
int num = 10; // 整数字面量
double pi = 3.14; // 浮点数字面量
char ch = 'A'; // 字符字面量
String str = "Hello"; // 字符串字面量
boolean flag = true; // 布尔字面量
// 符号引用
String className = "java.lang.String"; // 类全名
String methodName = "java.lang.String.length()I"; // 方法全名
System.out.println("Num: " + num);
System.out.println("Class Name: " + className);
System.out.println("Method Name: " + methodName);
}
}
(6). 总结
-
符号引用:用于描述类、字段和方法的全名,是一种逻辑引用,在类加载过程中被解析为直接引用。
-
字面量:直接表示常量值的符号,如整数、浮点数、字符、字符串等。
-
运行时常量池:存储符号引用和字面量,是方法区的一部分。
原文地址:https://blog.csdn.net/m0_73941339/article/details/145214076
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!