JVM体系结构
目录
一. JVM 规范
二. JVM 实现
(1) HotSpot
(2) JRockit
(3) IBM JDK(J9 VM)
(4) Azul Zing
(5) OpenJ9
三. JVM 实现的选择
四. JVM 的核心组件
五.JVM总结
六.Java 虚拟机(JVM)架构概述
1.Java 虚拟机(JVM)运行时数据区
(1). 程序计数器(Program Counter Register, PC Register)
(2). Java 虚拟机栈(Java Virtual Machine Stacks, JVM Stack)
(3). 堆(Heap)
(4). 方法区(Method Area)
(5). 运行时常量池(Run-Time Constant Pool)
(6). 本地方法栈(Native Method Stacks)
(7). 运行时数据区的关系
(8). 总结
2. 执行引擎(Execution Engine)
3. 本地方法接口(Native Method Interface, JNI)
4. 本地方法库(Native Method Library)
5. 线程共享与线程私有
七.HotSpot(Oracle JDK/Open JDK内部使用的JVM就是HotSpot)
1.JDK6的HotSpot
(1).JDK 6 中的 HotSpot 内存结构
堆(Heap):
①年轻代(Young Generation)
② 老年代(Old Generation)
方法区(永久代,Permanent Generation):
运行时常量池(Runtime Constant Pool):
③永久代(Permanent Generation)
(2). 符号引用
(3). 垃圾回收机制
① 年轻代垃圾回收(Minor GC)
② 老年代垃圾回收(Major GC/Full GC)
③ 永久代垃圾回收
(4). JDK 6 到 JDK 8 的变化
(5). 总结
2.JDK7的HotSpot
(1). 类的静态变量转移到堆中
(2). 字符串常量池转移到堆中
(3). 运行时常量池中的符号引用转移到本地内存
(4). JDK 7 的其他变化
(5). JDK 7 的内存结构总结
(6). 总结
3.JDK8及更高版本的HotSpot
(1). 彻底删除永久代
(2). 将方法区的实现转移到本地内存
(3). 将符号引用重新放回运行时常量池
(4). JDK 8 的内存结构总结
(5). JDK 8 的其他改进
(6). 总结
4.符号引用和字面量
(1). 符号引用(Symbolic References)
定义
作用
示例
特点
(2). 字面量(Literals)
定义
作用
示例
特点
(3). 符号引用和字面量的区别
(4). 运行时常量池(Run-Time Constant Pool)
(5). 示例代码
(6). 总结
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). 总结
-
符号引用:用于描述类、字段和方法的全名,是一种逻辑引用,在类加载过程中被解析为直接引用。
-
字面量:直接表示常量值的符号,如整数、浮点数、字符、字符串等。
-
运行时常量池:存储符号引用和字面量,是方法区的一部分。
相关文章:

JVM体系结构
目录 一. JVM 规范 二. JVM 实现 (1) HotSpot (2) JRockit (3) IBM JDK(J9 VM) (4) Azul Zing (5) OpenJ9 三. JVM 实现的选择 四. JVM 的核心组件 五.JVM总结 六.Java 虚拟机(JVM)架构概述 1.Java 虚拟机(…...
wandb使用遇到的一些问题
整合了一下使用wandb遇到的问题 1.请问下如果电脑挂了代理,应该怎么办呢?提示:Network error (ProxyError), entering retry loop. 在本地(而非服务器)运行代码时,常常因为开启代理而无法使用wandb&#…...

Java中的继承
引入继承 Java中使用类对实体进行描述,类经过实例化之后的产物对象,就可以用来表示现实中的实体,描述的事物错综复杂,事物之间可能会存在一些关联,因此我们就需要将他们共性抽取,面向对象的思想中提出了继…...

Cadence笔记--原理图导入PCB
1、以PMU6050为例,首先在原理图双击PMU6050器件,在PCB_Footprint目录填写PCB封装名称并且保存,如下图所示: 2、确保原理图命名的名称不一样,否则会出错,这里PMU6050更改了NC等名称,如下图所示&a…...

从AI生成内容到虚拟现实:娱乐体验的新边界
引言 在快速发展的科技时代,娱乐行业正经历一场前所未有的变革。传统的娱乐方式正与先进技术融合,创造出全新的沉浸式体验。从AI生成的个性化内容,到虚拟现实带来的身临其境的互动场景,科技不仅改变了我们消费娱乐的方式…...

【Linux】gdb_进程概念
📢博客主页:https://blog.csdn.net/2301_779549673 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢本文由 JohnKi 原创,首发于 CSDN🙉 📢未来很长&#…...

安全类脚本:拒绝ssh暴力破解
要求如下: 一个小时内,连续密码错误4次。 Linux lastb 命令用于列出登入系统失败的用户相关信息。 实验过程如下: 1. 创建两个IP地址不同的干净环境,分别是:192.168.46.101 Rocky 2 和 192.168.46.120 openEuler 2. 2.…...
Android15源码编译问题处理
最近想在Raspberry Pi5上面运行自己编译的Android15镜像,参考如下链接来处理: GitHub - raspberry-vanilla/android_local_manifest GitHub - raspberry-vanilla/android_kernel_manifest 代码同步完后,编译就出问题了,总是提示: FAILED: analyzing Android.bp files and…...

图解Git——分布式Git《Pro Git》
分布式工作流程 Centralized Workflow(集中式工作流) 所有开发者都与同一个中央仓库同步代码,每个人通过拉取、提交来合作。如果两个开发者同时修改了相同的文件,后一个开发者必须在推送之前合并其他人的更改。 Integration-Mana…...

Linux内核编程(二十一)USB应用及驱动开发
一、基础知识 1. USB接口是什么? USB接口(Universal Serial Bus)是一种通用串行总线,广泛使用的接口标准,主要用于连接计算机与外围设备(如键盘、鼠标、打印机、存储设备等)之间的数据传输和电…...
什么是数据仓库?
什么是数据仓库? 数据仓库(Data Warehouse,简称DW)是一种面向分析和决策的数据存储系统,它将企业中分散的、异构的数据按照一定的主题和模型进行集成和存储,为数据分析、报表生成以及商业智能(…...

计算机网络 (48)P2P应用
前言 计算机网络中的P2P(Peer to Peer,点对点)应用是一种去中心化的网络通信模式,它允许设备(或节点)直接连接并共享资源,而无需传统的客户端-服务器模型。 一、P2P技术原理 去中心化架构&#…...
SK海力士(SK Hynix)是全球领先的半导体制造商之一,其在无锡的工厂主要生产DRAM和NAND闪存等存储器产品。
SK海力士(SK Hynix)是全球领先的半导体制造商之一,其在无锡的工厂主要生产DRAM和NAND闪存等存储器产品。以下是SK海力士的一些主要产品型号和类别: DRAM 产品 DDR4 DRAM 特点: 高速、低功耗,广泛应用于PC、服务器和移…...
FunASR 在Linux/Unix 平台编译
第一步拉取镜像并生成容器: ### 镜像启动 通过下述命令拉取并启动FunASR软件包的docker镜像: shell sudo docker pull \ registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 mkdir -p ./funasr-runtime-…...

git操作(Windows中GitHub)
使用git控制GitHub中的仓库版本,并在Windows桌面中创建与修改代码,与GitHub仓库进行同步。 创建自己的GitHub仓库 创建一个gen_code实验性仓库用来学习和验证git在Windows下的使用方法: gen_code仓库 注意,创建仓库时不要设置…...

物联网网关Web服务器--Boa服务器移植与测试
1、Boa服务器介绍 BOA 服务器是一个小巧高效的web服务器,是一个运行于unix或linux下的,支持CGI的、适合于嵌入式系统的单任务的http服务器,源代码开放、性能高。 Boa 嵌入式 web 服务器的官方网站是http://www.boa.org/。 特点 轻量级&#x…...

vue3学习日记8 - 一级分类
最近发现职场前端用的框架大多为vue,所以最近也跟着黑马程序员vue3的课程进行学习,以下是我的学习记录 视频网址: Day2-17.Layout-Pinia优化重复请求_哔哩哔哩_bilibili 学习日记: vue3学习日记1 - 环境搭建-CSDN博客 vue3学…...

前端实习第二个月小结
时间飞快,第一次实习已经过去两个多月,作一些简单的总结和分享。 注:文章整体会比较轻松,提及的经历、经验仅作参考。 一、关于实习/工作内容 1、工作内容 近期做的是管理后台方面的业务,技术栈:前端re…...
深入了解卷积神经网络(CNN):图像处理与深度学习的革命性技术
深入了解卷积神经网络(CNN):图像处理与深度学习的革命性技术 导语 卷积神经网络(CNN)是现代深度学习领域中最重要的模型之一,特别在计算机视觉(CV)领域具有革命性的影响。无论是图…...

b站视频(网页加客户端)+本地视频 生成回链
b站视频(网页加客户端)本地视频 生成回链 引言 基于上一篇博客方案 本地视频进度加入笔记根据进度快速锁定视频位置 我想着只有本地的话, 那b站上的视频, 不是每次都得下载下来吗? 如果是一套课程, 直接下载, 然后视频处理成mp3,还好, 如果只是一个视频, 每次这样处理就有点…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...
文件上传漏洞防御全攻略
要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...

Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
从零手写Java版本的LSM Tree (一):LSM Tree 概述
🔥 推荐一个高质量的Java LSM Tree开源项目! https://github.com/brianxiadong/java-lsm-tree java-lsm-tree 是一个从零实现的Log-Structured Merge Tree,专为高并发写入场景设计。 核心亮点: ⚡ 极致性能:写入速度超…...