亲子游网站怎么做服装网站建设建议
目录
1.什么是JVM?
总结:JVM就是字节码的操作系统
2.说说JVM的组织架构
2.1JVM组织架构总览
2.2五大核心子系统详解
1.类加载子系统
1.1核心流程:类的生命周期(5个阶段)
1.2双亲委派模型
双亲委派模型的工作流程
双亲委派模型的核心优势
2.运行时数据区
3.执行引擎
4.本地方法接口(JNI,Java Native Interface)
5.本地方法库
2.3关键补充:JVM架构的“跨平台”本质
2.4总结
二,内存管理
3.能说一下JVM的内存区域(运行时数据区)吗
3.1各区域详解
1.程序计数器
2.虚拟机栈
3.本地方法栈(Native Method Stack)
4.堆(Heap)
5.方法区
3.2关键关联:各区域的数据交互
3.3总结
一、JDK 1.6 内存区域特点
二、JDK 1.7 内存区域变化
三、JDK 1.8 内存区域重大变更
五、变化的核心原因
三,垃圾回收
1.垃圾回收机制
1.1垃圾回收核心问题
1.2如何判断对象“已死”
1.3垃圾回收算法
1.标记-清除算法
2.复制算法
3.标记-整理算法
4.分代收集算法
1.4垃圾回收器(GC收集器)
1.CMS GC
2.G1 GC
1.5垃圾回收触发时机
六、内存分配与回收策略
1.什么是JVM?
JVM,也就是Java虚拟机,它是实现Java跨平台的基石。
程序运行之前,需要先通过编译器将java源代码文件编译成java字节码文件;
程序运行时,JVM会对字节码文件镜像逐行解释,翻译成机器码指令,并交给对应的操作系统去执行。
要理解JVM的本质,可以从为什么需要JVM和JVM具体做了什么两个角度来看:
除了字节码与机器码的转换,JVM还承担了运行时环境管理的全部职责,核心功能包括:
1.字节码的执行
2.自动内存管理
3.线程与并发管理
4.类加载机制
5.异常处理
6.安全控制
总结:JVM就是字节码的操作系统
如果把字节码比作“运行在JVM上的程序”,那么JVM相当于一个迷你的操作系统:它有自己的内存管理,进程(线程)调度,安全机制,而“将字节码翻译成机器码”只是它执行程序的手段之一。
简单来说,JVM的作用可以概况为:提供一个统一的运行时环境,负责字节码的执行,内存管理,并发控制等所有底层细节,让开发者只需关注业务逻辑,无需适配不同的硬件和操作系统
2.说说JVM的组织架构
JVM整体可划分为五大核心子系统。这些子系统系统工作,完成"将字节码转换成机器码并执行"的核心任务,同时负责内存管理,线程调度,安全校验等关键能力
2.1JVM组织架构总览
JVM的架构本质是”职责分层“的设计:从”接收字节码“到执行机器码”,从内存管理 到 ”异常处理“,每个子系统承担特定角色且依赖关系明确(如类加载子系统的输出是执行引擎的输入,内存区域是所有子系统的 “数据容器”)。
2.2五大核心子系统详解
1.类加载子系统
类加载子系统的核心职责是:将磁盘上的.class字节码文件(或网络,内存中的字节流)加载到JVM内存中,并转换为可直接使用的 ”运行时类对象(class对象)“。它时JVM执行字节码的”前置步骤“,遵循严格的”加载流程“和双亲委派模型。
1.1核心流程:类的生命周期(5个阶段)
一个类从加载到卸载,需经历5个阶段,其中前三个阶段由类加载子系统完成:
- 1.加载:通过类的全限定名(如java.lang.String)找到字节码文件,将字节流读取到内存,生成一个代表类的”二进制流“,并在内存中创建一个java.lang.Class对象(作为该类的”元数据载体“,存放在方法区)。
- 示例:通过
ClassLoader.loadClass("com.example.User")
触发加载。- 2.链接:堆加载后的类进行 ”验证,准备,解析“,确保类的合法性和可执行性:
- 验证:校验字节码是否符合JVM规范(),防止恶意字节码攻击。
- 准备:为类的静态变量(static)”分配内存(存放在方法区),并设置默认初始值(如int默认0,boolean默认false,而非代码中定义的初始值)。
- 例外:若静态变量被
final
修饰(如public static final int MAX = 100
),则此阶段直接赋值为 100(编译时已确定常量值)。- 解析:将类中的“符号引用”如(字节码中用字符串表示的类名,方法名)转换为“直接引用”(如内存地址),建立与其它类的关联。
- 3.初始化:执行类的静态代码块和静态变量的显示赋值语句,将静态变量设置为代码中定义的初始值
- 触发时机:只有当类被 “主动使用” 时才会初始化(如创建对象、调用静态方法、访问静态变量等)。
- 4.使用(Using):类的对象在内存中执行方法、访问字段,完成业务逻辑。
- 5.卸载(Unloading):类的
Class
对象被垃圾回收(需满足:该类所有对象已回收、加载该类的类加载器已回收),释放方法区内存。1.2双亲委派模型
双亲委派模型是Java类加载机制的核心原则,它定义了类加载器在加载类时的层级关系和委派规则,通过这种机制保证了Java类加载的安全性,一致性和效率。
- 什么时双亲委派模型
- 双亲委派模型的核心思想是:当一个类加载器需要加载某个类时,它首先不会自己尝试加载,而是将加载请求委派给其父类加载器;只有当父类加载器无法完成加载时,子加载器才会尝试去加载。
- 类加载器的层级结构
- 启动类加载器(Bootstrap ClassLoader)加载 Java 核心类库
- 扩展类加载器(Extension ClassLoader)加载 Java 扩展类库
- 应用程序类加载器(Application ClassLoader)加载当前应用程序的类路径(
classpath
)下的类,包括开发者编写的类和第三方 jar 包中的类。- 自定义类加载器(Custom ClassLoader)满足特殊的类加载需求(如加载加密的类、从网络加载类、热部署类等)。
双亲委派模型的工作流程
- 检查是否已加载:首先检查当前类加载器是否已经加载过该类,若已加载则直接返回(避免重复加载)。
- 委派给父类加载器:若未加载,则将加载请求委派给其父类加载器。
- 递归委派:父类加载器重复步骤 1 和 2,继续将请求委派给更上层的父类加载器,直至传递到启动类加载器。
- 父类尝试加载:启动类加载器尝试在自己的搜索范围内加载该类,若能加载则返回 Class 对象。
- 逐层回退尝试:若启动类加载器无法加载,则由其子类加载器(扩展类加载器)尝试加载;若仍无法加载,再由下一层的类加载器(应用程序类加载器)尝试;以此类推。
- 自身加载:若所有父类加载器都无法加载,最终由发起请求的子加载器尝试在自己的搜索范围内加载该类。若成功则返回 Class 对象,否则抛出
ClassNotFoundException
。双亲委派模型的核心优势
保证类的唯一性和一致性
避免同一个类被多个类加载器重复加载。例如,java.lang.String
类只会被启动类加载器加载一次,无论哪个类加载器发起请求,最终都会委派到启动类加载器,确保所有代码使用的String
类是同一个 Class 对象。防止核心类被篡改
保护 Java 核心类库的安全。假设有人恶意编写了一个java.lang.String
类并放在classpath
下,由于双亲委派机制,该类会被委派给启动类加载器,而启动类加载器只会加载核心类库中的String
类,恶意类不会被加载,从而避免了核心类被替代的风险。简化类加载器的实现
子加载器无需关心父类加载器已覆盖的类加载范围,只需专注于自己负责的类,减少了重复开发。2.运行时数据区
运行时数据区是JVM在内存中划分的“数据存储区域”,用于存放类的元数据,对象实例,线程执行状态等数据。该区域进一步划分为5个部分,其中“方法区”和“堆”是线程共享的(所有线程可访问),”虚拟机栈“ ”本地方法“ ”栈程序计数器“是线程私有的
3.执行引擎
执行引擎是JVM的“核心执行单元”,负责将加载到内存中的字节码转换为操作系统可执行的机器码并执行。由于字节码是“平台无关的中间代码”,执行引擎需要通过特定技术实现“跨平台执行”,主要有三种执行方式:
- (1)解释执行
- 原理:通过“解释器”逐行读取字节码指令,每读取一条就翻译成对应的机器码并立即执行。
- 优点:启动速度块(无需提前编译),适合“短时间,小范围”的代码执行(如启动阶段)。
- 缺点:执行效率低(同一指令重复执行时需重复翻译),无法充分利用硬件性能。
- (2)编译执行
- 原理:通过“即时编译器(JIT)”将“热点代码”(被频繁执行的代码,如循环,高频调用的方法)一次性编译为机器码并缓存,后续执行直接使用缓存的机器码。
- 优点:执行效率高(避免重复翻译),是JVM性能优化的核心
- 缺点:编译过程需要耗时(启动阶段会有“预热”过程),适合“长时间,高频次”的代码执行。
- (3)混合执行
- 现状:现代JVM均采用“解释执行 + 编译执行”的混合模式:
- 1.启动时,通过解释器快速执行代码,保证启动速度;
- 2.运行过程中,JIT编译器后台分析“热点代码”,将其编译为机器码并替换解释执行的逻辑,提升长期执行效率。
4.本地方法接口(JNI,Java Native Interface)
本地方法接口时域“本地方法库”交互的“桥梁”其核心作用时:允许Java代码调用本地方法(非Java编写的方法),同时允许本地方法调用Java代码
- 使用场景:当需要调用操作系统底层API(如文件操作,网络通信),硬件驱动,或需要高性能计算(如数值分析)时,Java代码无法直接实现,需通过JNI调用C/C++编写的本地方法。
- 示例:
java.lang.Thread
类的start0()
方法就是本地方法(由 C 实现线程创建逻辑),Java 代码通过start()
调用start0()
,最终触发操作系统创建线程。5.本地方法库
本地方法库“本地方法的实现集合”,通过C/C++编写,存放于操作系统的动态链接库中(如 Windows 的
.dll
文件、Linux 的.so
文件、macOS 的.dylib
文件)。
- JVM 在执行本地方法时,会通过 JNI 找到对应的本地方法库,并加载执行其中的代码;
- 常见的本地方法库包括:JDK 自带的
rt.jar
中关联的本地库(如处理 IO 的java.io
包依赖的本地库)、第三方库(如 OpenCV、FFmpeg 等)。2.3关键补充:JVM架构的“跨平台”本质
- JVM的组织架构设计直接支撑了Java的“跨平台”特性:
- 字节码的平台无关性:Java代码编译后生成的.class文件时字节码(与操作系统无关),可在任何安装了JVM的平台上运行;
- JVM的平台相关性:不同的操作系统(Windows、Linux、macOS)需要安装对应的JVM版本如 Windows 版 JVM、Linux 版 JVM),其“执行引擎”会将字节码翻译为当前操作系统的机器码,“本地方法库”也会适配当前系统的API。
简言之:Java代码跨平台,JVM跨平台(不同的系统有不同的JVM),最终实现“一次编写,到处运行”。
2.4总结
JVM的组织架构是一个“分工明确,协同高效”的系统:
- 类加载子系统负责”输入“(将字节码加载为运行时类);
- 运行时数据区负责”存储“(存放类的元数据,对象,线程状态);
- 执行引擎负责”核心执行“(将字节码转换为机器码);
- 本地方法接口和本地方法库负责”扩展能力“(对接操作系统底层)。
理解这一架构,是掌握JVM内存模型,垃圾回收,性能优化等核心知识的基础。
二,内存管理
3.能说一下JVM的内存区域(运行时数据区)吗
这种划分的本质时:线程共享区域存放”全局数据“(如对象实例,类元数据),线程私有区域存放”线程执行状态“(如方法调用栈,指令执行位置)。
3.1各区域详解
1.程序计数器
定义:一块较小的内存空间,可理解为”当前线程执行的字节码指令的行号指示器“。
- 核心作用:
- 记录当前线程正在执行的字节码指令地址(如第N条指令)。
- 当线程切换(如CPU时间片用完)后,通过程序计数器恢复执行位置,保证线程切换后的执行连续性。
- 特殊规则:
- 若线程执行的是 Java 方法,计数器记录的是当前字节码指令的地址(偏移量)。
- 若线程执行的是本地方法(Native Method),计数器值为0(因为本地方法由 C/C++ 实现,不遵循 Java 字节码规范)。
- 内存特性:
- 唯一不会抛出OutOfMemoryError的区域(内存固定且极小)
- 线程私有,每个线程都有独立的程序计数器,互不干扰。
2.虚拟机栈
定义:线程执行Java方法时的”内存栈“,用于存储方法调用的状态信息。
- 核心结构:栈帧(Stack Frame)
- 每个Java方法的调用都会创建一个栈帧并压入虚拟机栈,方法执行完毕后栈帧出栈。一个栈帧包含以下信息:
- 局部变量表:存放方法的局部变量(如基本数据类型,对象引用,returnAddress类型),容量在编译期确定(通过字节码中的
max_locals
属性)。- 操作数栈:方法执行过程中用于临时存储操作数的 “栈结构”(如执行
a + b
时,先将 a、b 入栈,再执行加法出栈),容量在编译期确定(通过max_stack
属性)。- 动态链接:指向当前方法在方法区中元数据的引用(支持 “晚期绑定”,即运行时确定方法的实际实现)。
- 方法返回地址:方法执行完毕后,返回调用者的指令地址(如正常返回或异常返回)。
- 内存特性:
- 线程私有,栈的深度由方法调用层级决定。
- 若方法调用层级过深(如无限递归),会导致栈帧数量超过栈的最大容量,抛出StackOverflowError。
- 若虚拟机栈无法申请到足够内存(如创建线程时),会抛出OutOfMemoryError。
3.本地方法栈(Native Method Stack)
定义:与虚拟机栈功能类似,但专门用于执行本地方法(由 C/C++ 编写的方法,如
System.currentTimeMillis()
)。核心作用:存储本地方法的调用状态(如参数,返回值,局部变量等)。
实现差异:
- 《Java 虚拟机规范》对本地方法栈的要求宽松,不同 JVM 实现可自由设计(如 HotSpot 虚拟机直接将本地方法栈与虚拟机栈合并)。
内存特性:
- 线程私有,与虚拟机栈共享相同的异常类型(StackOverflowError 和 OutOfMemoryError)。
4.堆(Heap)
JVM中最大的内存区域,是所有对象实例和数组的诞生地(《Java 虚拟机规范》明确:“所有对象实例及数组都在堆上分配”)。
核心作用:
- 存储对象的实例数据(如对象的字段值)。
- 是垃圾回收(GC)的核心区域(几乎所有的GC算法都围绕堆设计)。
内存划分(逻辑分区):
为了优化垃圾回收效率,堆通常按对象的”存活周期“划分为以下区域:
、
- 新生代 GC(Minor GC):针对新生代的垃圾回收,频率高、速度快。
- 老年代 GC(Major GC/Full GC):针对老年代的垃圾回收,频率低、速度慢(通常会伴随 Minor GC)。
- 内存特性:
- 线程共享,所有线程创建的对象都存放在堆中。
- 堆的大小可通过 JVM 参数配置(如
-Xms
设置初始堆大小,-Xmx
设置最大堆大小)。- 若堆中没有足够内存分配对象,且无法通过 GC 释放空间时,会抛出OutOfMemoryError: Java heap space。
5.方法区
定义:用于存储类的元数据信息,是”类的信息仓库“。
存储内容:
- 类的结构信息(如类的名称,访问修饰符,父类,接口列表)。
- 常量池(存放编译期生成的字面量和符号引用,如字符串常量,字段和方法的引用)。
- 静态变量(被static修饰的变量,属于类而非对象)。
- 方法的字节码和符号表(如方法参数,返回值类型)。
- 及时编译器(JIT)编译后的代码缓存。
实现变迁(以HotSpot)为例:
- JDK7以前:方法区通过”永久代(PermGen)“实现,永久代是堆的一部分,大小固定
- JDK8以后:永久代被移除,方法区通过”元空间(Metaspace)“实现,元空间使用本地内存(而非JVM堆内存),默认无大小限制
- 变迁原因:永久代大小难以控制(易出现OOM),元空间使用本地内存更灵活,且能更好地支持动态类加载(如Spring/CGLib动态生产类)
内存特性:
- 线程共享,所有线程可访问类的元数据。
- 方法区的内存回收主要针对 “无用的类”(需满足:该类所有实例已回收、加载该类的类加载器已回收、该类的
Class
对象无引用)。- 若方法区无法分配足够内存(如大量动态生成类),会抛出OutOfMemoryError: Metaspace(JDK 8+)或OutOfMemoryError: PermGen space(JDK 7 及之前)。
3.2关键关联:各区域的数据交互
运行时数据区的各个部分并非孤立存在,而是通过数据引用形成紧密关联:
- 1.虚拟机栈中的”对象引用“指向堆中的对象实例
- 2.堆中的对象实例通过”元数据指针“指向方法区中该类的元数据
- 3.方法区中的常量池引用堆中的字符串对象(如
String s = "abc"
中,"abc"
存放在堆,常量池存放其引用)。- 4.程序计数器通过地址关联虚拟机栈中当前执行的栈帧。
例如,执行
User user = new User()
时的内存交互:
new User()
在堆中创建User
对象实例。user
作为局部变量,存放在虚拟机栈的局部变量表中,指向堆中的User
实例。- 堆中的
User
实例通过元数据指针,关联方法区中User
类的元数据(类结构、方法字节码等)。
3.3总结
JVM运行时数据区是Java内存关联的核心,其设计直接影响程序的性能和稳定性:
- 线程私有区域管理”执行流状态“,内存随线程生命周期自动回收,无需GC干预。
- 线程共享区域管理”数据存储“,需要GC机械能内存回收(堆是GC的主要战场,方法区回收频率低)
3.2
JDK 1.6 到 JDK 1.8 期间,JVM 内存区域的核心变化集中在方法区的实现方式和字符串常量池的存储位置上,这些变化直接影响了内存管理策略和垃圾回收行为。以下是各版本的关键差异:
一、JDK 1.6 内存区域特点
方法区实现:
方法区通过永久代(PermGen) 实现,属于堆的一部分,有固定大小限制(可通过-XX:PermSize
和-XX:MaxPermSize
配置)。
存储内容包括:类元数据、静态变量、常量池(含字符串常量)、JIT 编译后的代码等。字符串常量池:
字符串常量池(String Pool)存放在永久代中。
例如,String s = "abc"
中,"abc"
作为字符串常量存储在永久代的常量池里。静态变量存储:
静态变量(static
修饰)存放在永久代中,与类的元数据绑定。二、JDK 1.7 内存区域变化
方法区实现:
仍使用永久代实现方法区,但永久代的功能开始弱化,部分内容迁移到堆中。字符串常量池迁移:
字符串常量池从永久代移至堆中。
这是最重要的变化:"abc"
等字符串常量不再存放在永久代,而是存放在堆内存中,由 GC 直接管理。
好处:避免因永久代空间不足导致的OutOfMemoryError: PermGen space
(尤其是大量字符串操作场景)。静态变量存储调整:
静态变量从永久代迁移到堆中(存储在对应的类对象所在的堆内存中)。
例如,User
类的静态变量count
随User.class
对象一起存放在堆中,而非永久代。三、JDK 1.8 内存区域重大变更
永久代被彻底移除,引入元空间(Metaspace):
- 方法区的实现从永久代改为元空间(Metaspace),元空间不再属于堆内存,而是使用本地内存(Native Memory)。
- 元空间的大小默认不受 JVM 堆大小限制,仅受限于操作系统的可用内存(可通过
-XX:MetaspaceSize
和-XX:MaxMetaspaceSize
配置上限)。- 存储内容:类元数据、方法字节码、JIT 编译后的代码等(与之前方法区一致,但存储位置和内存管理方式改变)。
字符串常量池位置不变:
继续保留在堆中(延续 JDK 1.7 的设计),由 GC 管理。静态变量存储:
保持 JDK 1.7 的设计,仍存放在堆中(与类对象绑定)。五、变化的核心原因
解决永久代内存管理问题:
永久代大小难以预估(尤其是动态生成类的场景,如 Spring/CGLib),容易导致PermGen OOM
。元空间使用本地内存,大小更灵活,减少了内存溢出风险。优化垃圾回收效率:
字符串常量池和静态变量迁移到堆中后,可由 GC 直接回收,避免了永久代回收效率低的问题(永久代 GC 频率低,且回收逻辑复杂)。与 HotSpot 其他技术适配:
元空间的设计更符合 HotSpot 对内存管理的整体规划,便于与 JIT 编译器、类加载器等模块协同优化。这些变化使得 JDK 1.8 的内存管理更灵活、更高效,尤其适合大规模应用和动态类加载场景。
三,垃圾回收
JVM 的垃圾回收(Garbage Collection,GC)机制是自动内存管理的核心,负责识别并回收不再使用的对象所占用的内存,避免内存泄漏和溢出。其设计目标是在保证程序正确性的前提下,最大化内存利用率和程序性能。
1.垃圾回收机制
1.1垃圾回收核心问题
垃圾回收机制需要解决三个问题:
- 1.哪些内存需要回收?->识别”死亡对象“
- 2.何时回收时hi收?->回收时机策略(如内存不足时,定时触发等)
- 3.如何回收->垃圾回收算法与实现
1.2如何判断对象“已死”
JVM通过可达性分析判定对象是否存活:
- 核心思想:以“GC Roots”为起点,遍历对象引用链,不可达的对象被标记为“可回收”。
- GC Roots包含:
- 虚拟机栈中局部遍历表的引用对象(如方法参数,局部变量)
- 方法区中静态变量和常量的引用(static Object obj)
- 本地方法栈JNI(Native方法)的引用对象
- 活跃线程的引用
- 特殊情况:对象的“自我救赎”
- 即使对象被标记为不可达,也并非一定会被回收,因为存在“finalize()”方法的救赎机会:
- 1.第一次标记:对象不可达,被标记并帅选(是否需要执行finalize())
- 2.若未重写finalize()或已执行过,则直接判断为死亡
- 3.否则,对象被放入F-Queue队列,由Finalizer线程执行finalize()
- 4.第二次标记:若对象在finalize()中重新建立与GCRoots的连接(如赋值给某个引用),则被移除 “即将回收” 集合
- 注意:finalize () 执行优先级低且不确定性强,不建议使用(已被 JDK 9 标记为 Deprecated)。
1.3垃圾回收算法
JVM针对不同内存区域(老年代,新生代)设计了不同的回收算法,核心算法包括:
1.标记-清除算法
- 流程
- 1.标记:从GC Roots遍历,标记所有可达对象
- 2.清除:回收所有未标记的对象,释放内存
- 优点:实现简单,无需移动对象
- 缺点:
- 效率低(标记和清除都需要遍历对象)
- 产生内存碎片(回收后内存空间不连续,影响大对象分配)
- 适用场景:老年代(对象存活率高,移动成本大)
2.复制算法
- 流程:
- 1.将内存分为大小相等的两块(From区和To区)
- 2.仅使用From区,To区空闲
- 3.回收时,将From区中存活的对象复制到To区
- 4.情况From区,交换From和To的角色、
- 改进版(新生代实际使用):
- 内存分为Eden(80%),Survivor(10%),Survivor(10%)
- 新对象优先分配到Eden,回收时将存活对象复制到Survivor0
- 下次回收时,将Eden+Survivor0对象复制到Survivor1
- 反复交换,对象年龄达标后进入老年代
- 优点:
- 效率高(只需复制存活对象)
- 无内存碎片(内存连续分配)
- 缺点:内存利用率低(始终有一块空闲区域)
- 适用场景:新生代(对象存活率低,复制成本小)
3.标记-整理算法
- 流程:
- 1.标记:同标记-清除算法,标记所有可达对象
- 2.整理:将存活对象向内存一端移动,然后清理边界外的内存
- 优点:
- 解决标记-清除的内存碎片问题
- 内存利用率100%(无需预留空闲区)
- 缺点:增加了对象移动的成本(需更新引用地址)
- 适用场景:老年代(对象存活率高,需避免碎片)
4.分代收集算法
- 核心思想:根据对象存活周期划分区域,采用不同算法
- 新生代:对象存活时间短——>复制算法
- 老年代:对象存活时间长——>标记-清除/标记-整理算法
- 优势:结合不同区域特定,最大化回收效率(JVM默认采用)
1.4垃圾回收器(GC收集器)
垃圾回收器是算法的具体实现,JDK提供了多种收集器
重点收集器详解:
1.CMS GC
- 目标:最短回收停顿时间
- 流程:
- 初始标记(STW):标记GCRoot直接关联对象
- 并发标记:与用户线程并行,遍历引用链
- 重新标记(STW):修正并发标记期间的变动
- 并发清除:与用户线程并行,回收未标记对象
- 缺点:
- 占用CPU资源高(并发阶段与用户线程竞争)
- 产生内存碎片(标记-清除算法)
- 无法处理浮动垃圾(并发清除时新产生的垃圾)
2.G1 GC
- 创新点:将堆划分为多个大小相等的Region(1MB-32MB)
- 特点:
- 按Region回收,优先回收垃圾多的Region
- 兼顾吞吐量和延迟(可设置最大停顿时间)
- 支持跨Region引用(通过Remembered Set追踪)
- 流程:
- 初始标记(STW)
- 并发标记
- 最终标记(STW)
- 筛选回收(STW,选择部分Region回收)
1.5垃圾回收触发时机
1.Minor GC:发生在新生代
- 触发条件:Eden区满时(分配对象失败)
- 特点:频率高,回收速度块STW时间短
2.Major GC/Full FC:主要回收老年代
触发条件:
- 老年代空间不足
- 方法区(元空间)不足
- 调用System.gc()
- MinorGC后存活对象无法放入Survivor区(直接进入老年代,导致老年代空间不足)
特点:频率低,回收速度慢STW时间长(应尽量避免)
六、内存分配与回收策略
JVM 通过一系列策略优化对象分配和回收效率:
对象优先在 Eden 区分配
新对象默认分配到 Eden 区,当 Eden 区满时触发 Minor GC。大对象直接进入老年代
超过-XX:PretenureSizeThreshold
参数的对象(如大数组)直接分配到老年代,避免在新生代频繁复制。长期存活对象进入老年代
对象在 Survivor 区每经历一次 Minor GC,年龄 + 1,达到-XX:MaxTenuringThreshold
(默认 15)后进入老年代。动态对象年龄判定
若 Survivor 区中相同年龄的对象总大小超过该区一半,年龄≥该年龄的对象直接进入老年代(无需等待阈值)。空间分配担保
Minor GC 前,JVM 检查老年代最大可用连续空间是否≥新生代所有对象总大小:
- 是:安全,执行 Minor GC
- 否:检查是否允许担保失败(
-XX:+HandlePromotionFailure
)
- 允许:若老年代可用空间≥历次晋升平均大小,尝试 Minor GC
- 不允许:直接触发 Full GC