JVM(Java虚拟机)是Java跨平台特性的核心。它是一个虚构的计算机,通过在实际计算机上模拟计算机功能来实现的。
JVM的作用
- 跨平台运行:Java源码编译为字节码后(.class文件),jvm负责将字节码解释或编译为本地机器码执行。实现“一次编写,多处运行“。
- 内存管理:自动进行内存分配和垃圾回收,减少手动内存管理的错误。
- 安全控制:通过字节码验证器等机制确保代码安全执行。
核心组件
- 类加载器(ClassLoader):负责将.class文件加载到JVM中,分为BootStrap ClassLoader(加载核心类库)、Extension ClassLoader(加载扩展类库)、ApplicationClassLoader(加载应用程序)。
- 运行时数据区:
- 方法区:存储类信息,常量、静态变量(JDK8之后移至元空间Metaspace,使用本地内存)
- 堆:存储对象实例,是垃圾回收的主要区域,线程共享。
- 虚拟机栈:每个线程私有,存储方法调用的栈帧(包含局部变量表,操作数栈等)。
- 本地方法栈:类似虚拟机栈,但为Native方法服务。
- 程序计数器:记录当前线程执行的字节码指令地址,线程私有。
- 执行引擎:负责执行字节码,包括解释器(逐行解释执行)和即时编译(JIT,将热点代码编译为机器码缓存)。
- 垃圾回收器(GC):自动回收堆中不再使用的对象内存。
垃圾回收GC
- 回收区域:主要针对堆内存(新生代和老年代)
- 判断对象存活:常用可达性分析算法,通过GC Roots(如虚拟机栈引用的对象,静态变量引用的对象等)判断对象是否可达。
- 垃圾收集算法:
- 标记-清除:标记需要回收的对象,然后清除,会产生内存碎片。
- 标记-复制:将内存分为两块,只使用一块,回收时将存活对象复制到另一块,适用于新生代。
- 标记-整理:标记后将存活对象向一端移动,然后清除边界外内存,使用老年代。
- 常见收集器:Serial GC、ParelleGC、CMS(Concurrent Mark Sweep)、G1、ZGC、Shenandoah等,各有不同性能特点。
垃圾回收的核心目标
- 识别垃圾:判断哪些对象已经不再被程序使用(”死亡对象")。
- 回收内存:释放死亡对象占用的内存,供新对象使用。
- 高效执行:在不显著影响程序运行的前提下完成回收。(低延迟、高吞吐量)
判断对象“死亡”
JVM通过可达性分析算法判断对象是否存活。
- GC Roots:作为根节点的对象(如虚拟机栈中引用的对象、静态变量引用的对象、常量池引用的对象、本地方法栈中引用的对象等)。
- 可达性链条:从GC Roots出发,所有能被直接或间接引用的对象都是“存活”的,反之,无法到达的对象被标记为“死亡”。
垃圾回收算法
JVM根据**不同内存区域的特点(如对象存活时间)**采用不同的回收算法:
- 标记-清除算法(Mark-Sweep)
- 步骤:先标记所有死亡的对象,在统一清除这些对象。
- 优点:简单直接。
- 缺点:会产生大量内存碎片,可能导致后续 大对象 无法分配内存。
- 标记-复制算法(Mark-Copy)
- 将内存分为两块 (如Eden区 和 Survivor区),只是用其中一块,回收时将存活对象复制到另一块,然后清空原内存块。
- 优点:无内存碎片,实现简单。
- 缺点:内存利用率低(仅利用一半),适合对象存活率低的场景(如新生代)。
- 标记-整理算法(Mark-Compact)
- 步骤:标记死亡对象后,将所有存活对象向内存 一端移动,然后清除边界外的内存。
- 优点:无内存碎片,内存利用率高。
- 缺点:移动对象的成本高,适合对象存活率高的场景(如老年代)。
堆内存的分代 回收策略
JVM将堆内存划分为不同区域,针对各自区域特点采用不同的回收策略(分代思想)
- 新生代(Young Generation)
- 存储新创建的对象,对象存活率低(大部分很快死亡)
- 分为Eden区和Survivor区 (From区和To区,大小通常1:1)
- 回收流程:
- 新对象优先分配大Eden区。
- Eden区满时触发Mintor GC(新生代回收),存活对象复制到From Survivor区。
- 再次回收时,Eden区和From区的存活对象复制到To区,年龄+1。
- 当对象年龄到达15岁时,晋升老年代
- 老年代(Old Generation)
- 存储存活时间长的对象(如缓存对象),存活率高。
- 次啊用标记-整理算法,回收频率低。
- 当老年代内存不足时,触发Major GC/Full GC(全堆回收,包括新生代和老年代),耗时较长
- 元空间
- 存储类信息、常量池等,替代JDK7的永久代(Perm Gen)。
- 使用本地内存,默认无上限(可通过参数设置)。一般不参与垃圾回收
类加载机制
- 加载过程:加载(获取字节流) - 验证(确保字节码安全)- 准备(为静态变量分配内存并设置默认值) - 解释(将符号引用转为直接引用)-初始化(执行类构造器《clinit()方法)
- 双亲委派模型:加载类时,先委托父类加载器加载,父类无法加载时才由子类加载器尝试,避免类的重复加载和核心类篡改。
JVM调优
- 调优参数:通过JVM参数调整内存大小(如:-Xms、-Xmx),设置初始化堆大小,选择合适的垃圾收集器(如:XX:Use G1GC),设置新生代和老年代比例等。
- 性能监控工具:jsp(查看进程)、jstat(统计信息)、jmap(内存映射)、jstack(线程堆栈)、Visual VM等。
hotSpot VM 介绍
核心工作原理:字节码执行和内存管理。通过 热点代码优化 和 分代垃圾回收 实现。
类加载:将字节码转为可执行的类对象。
HotSpot首先通过**类加载器(Class Loader)**完成类的加载,确保籽伽玛合规并转为 JVM可识别的结构。遵循 双亲委派模型。
- 加载:从磁盘/网络中 加载 .class文件(字节码),将其转为JVM内部的“类元数据”(存储在方法区/元空间)。
- 验证:校验字节码合法性(格式、语法和安全权限),防止恶意代码执行。
- 准备:为类的静态变量分配内存并设置默认值,
- 解析:将类中引用(如类名、方法名)转为直接引用(内存地址)。
- 初始化:执行静态代码和静态变量的显式赋值,此时类可使用。
字节码执行:混合”解释执行“和“即时编译”
HotSpot不直接执行字节码,而是通过 ”解释器“+JIT及时编译” 混合模式,平衡 启动速度和运行性能。
- 第一步:解释执行(启动阶段)
- 第二步:热点代码探测和JIT编译(运行阶段)
内存管理:分代模型 + 多垃圾回收适配
HotSpot基于 “对象生命周期差异”设计分代内存模型,并适配不同的垃圾回收模型。核心逻辑:
- 内存分代划分(堆内存):
- 新生代:
- 老年代:
- 元空间:
- 垃圾回收触发:
- 新生代满,触发Minor GC。
- 老年代/元空间 满触发MajorGC/Full GC (回收老年代和新生代),会导致程序停顿(STW),需要通过G1/ZGC等回收器减少停顿时间。
线程与并发:轻量级同步机制
HotSpot通过对象头(Mark Word)和监视器(Monitor)实现Java并发同步,核心是锁优化以减少阻塞开销。
- 锁状态升级: