JVM基础总结

1. JVM 架构与运行机制

1.1 核心职责

JVM 是运行在计算机上的程序,负责运行 Java 字节码文件。其核心功能包括:

  • 解释与运行:将字节码指令实时解释为机器码。Java 性能不如 C++ 的主要原因在于运行时的实时解释 。
  • 内存管理:自动为对象分配内存并回收不再使用的对象(垃圾回收机制),解决了 C/C++ 中手动释放内存易导致内存泄漏或悬空指针的问题 。
  • 即时编译 (JIT):为了优化性能,JVM 会识别"热点代码"(高频调用的方法或循环),将其编译为机器码并保存在内存中(CodeCache),下次直接调用,从而获得接近 C++ 的性能 。

1.2 跨平台原理

Java 的"一次编写,到处运行"依赖于 JVM。不同操作系统(Windows, Linux)有不同版本的 JVM 实现。源代码编译成统一的字节码文件(.class),由特定平台的 JVM 将其翻译成该平台对应的机器码 。

2. 字节码文件深度解析

2.1 文件结构详解

字节码文件是以二进制流存储的,主要包含:

  • 魔数 (Magic Number) :前4个字节固定为 0xcafebabe,用于校验文件类型 。
  • 版本号:主版本号 52 代表 JDK 8。计算公式:JDK版本 = 主版本号 - 44 。
  • 常量池:存储字面量(如字符串 "abc")和符号引用(类名、字段名)。字节码指令通过编号引用常量池内容,避免重复定义,节省空间 。
  • 方法表:包含方法的字节码指令(Code属性)。

2.2 字节码指令案例分析

通过 i++++i 的字节码差异理解执行逻辑:

  • i++ 原理 :先将局部变量 i 的值压入操作数栈(iload),然后直接在局部变量表中对 i 加 1(iinc),最后将栈中原来的值赋回给目标变量。因此 i=i++ 结果仍为原值 。
  • ++i 原理 :先在局部变量表中对 i 加 1(iinc),然后再将加 1 后的值压入操作数栈(iload) 。

2.3 常用工具

  • jclasslib:可视化查看字节码文件结构(常量池、接口、属性等) 。
  • javap :JDK 自带反编译工具,javap -v 可查看详细信息 。
  • Arthas :阿里开源的线上诊断工具。
    • dump:将内存中的字节码保存到本地 。
    • jad:反编译运行中的类代码,检查源码是否一致 。
    • retransform:实现类的热替换(热部署),用于线上不停机修复 Bug 。

3. 类的生命周期与加载机制

类的生命周期包括:加载 -> 连接 (验证、准备、解析) -> 初始化 -> 使用 -> 卸载

3.1 加载过程 (Loading -> Linking -> Initializing)

  1. 加载 (Loading) :通过类加载器获取字节码流,在方法区生成 InstanceKlass,在堆中生成 java.lang.Class 对象 。

  2. 连接 (Linking)

    • 验证:检查文件格式(魔数、版本号)和元信息 。

    • 准备 :为静态变量分配内存并赋默认初值 (如 int 为 0)。注意:final 修饰的基本类型静态变量会在准备阶段直接赋代码中的值 。

    • 解析:将符号引用替换为直接引用(内存地址) 。

  3. 初始化 (Initialization) :执行 <clinit> 方法。该方法由静态代码块和静态变量赋值语句组成。

    • 执行顺序:按代码编写顺序执行。父类 <clinit> 优先于子类执行 。
    • 触发条件:new 对象、访问静态变量(非 final 常量)、Class.forName 等 。

3.2 类加载器与双亲委派

  • 加载器层级
    • 启动类加载器 (Bootstrap) :加载 JAVA_HOME/jre/lib 下的核心类(如 String),由 C++ 实现,Java 中获取为 null
    • 扩展类加载器 (Extension) :加载 jre/lib/ext 下的类 。
    • 应用程序类加载器 (Application):加载 classpath 下的类(项目代码)。
  • 双亲委派机制
    • 流程:向上查找缓存是否加载 -> 向上委托加载 -> 顶层无法加载则向下尝试加载 。
    • 作用 :保证核心类库安全(防止自定义 java.lang.String 覆盖核心类),避免类重复加载。
  • 打破双亲委派
    • 自定义类加载器 :重写 loadClass 方法。Tomcat 使用此机制隔离不同 Web 应用的同名类 。
    • 线程上下文类加载器 :JDBC 利用 SPI 机制,通过 Thread.currentThread().getContextClassLoader() 加载驱动实现类,打破了"启动类加载器不能依赖应用类加载器"的限制 。

4. 运行时数据区 (内存模型)

4.1 线程不共享区域

  • 程序计数器:记录下一条字节码指令的地址。是唯一不会发生内存溢出的区域 。
  • Java 虚拟机栈
    • 栈帧 :每个方法调用对应一个栈帧,包含局部变量表操作数栈帧数据(动态链接、返回地址) 。
    • 局部变量表 :存放 this、参数和局部变量。槽位(Slot)可复用 。
    • 栈溢出 :递归调用过深会导致 StackOverflowError。可通过 -Xss 调整栈大小(如 -Xss256k) 。
  • 本地方法栈 :存储 native 方法的栈帧 。

4.2 线程共享区域

  • 堆 (Heap)
    • 存储对象实例。
    • 参数设置-Xmx (最大值) 和 -Xms (初始值) 建议设置为相同,避免运行时申请内存的开销和堆收缩 33。
    • 查看命令arthasdashboardmemory 命令查看 used/total/max
  • 方法区 (Method Area)
    • 存储类元信息、运行时常量池。
    • 实现变迁 :JDK 7 使用永久代 (PermGen) (在堆中);JDK 8 使用元空间 (Metaspace)(直接内存)。
    • 参数 :JDK 8 使用 -XX:MaxMetaspaceSize 限制元空间大小,防止耗尽系统内存 。
    • 字符串常量池 :JDK 7 以后从方法区移到了堆中。String.intern() 在 JDK 7+ 中,如果池中没有,会直接存储堆中对象的引用,而不是复制对象 。
  • 直接内存 :NIO 使用,不属于 JVM 运行时数据区。通过 ByteBuffer.allocateDirect 分配,读写性能高(零拷贝),但分配回收成本高。需防范内存泄漏 。

5. 垃圾回收 (GC)

5.1 对象存活判断

  • 可达性分析法 :从 GC Root 出发,不可达的对象视为垃圾。
  • GC Root 包括:线程栈中引用的对象、静态变量引用的对象、JNI 引用的对象、监视器锁对象 。
  • 引用类型
    • 强引用:普通引用,宁可 OOM 也不回收。
    • 软引用 (Soft):内存不足时回收,适用于缓存 。
    • 弱引用 (Weak):无论内存是否充足,GC 时都会回收。
    • 虚引用 (Phantom):用于接收对象回收通知(如管理直接内存) 。

5.2 垃圾回收算法

  • 标记-清除:简单,但产生内存碎片,大对象无法分配 。
  • 复制算法 :无碎片,吞吐量高,但内存利用率只有一半。适用于年轻代
  • 标记-整理 :无碎片,利用率高,但整理阶段效率低。适用于老年代

5.3 垃圾回收器

回收器 适用区域 算法 特点
Serial / Serial Old 年轻代/老年代 复制 / 标记-整理 单线程,适合客户端简单场景 。
ParNew 年轻代 复制 多线程,常与 CMS 配合 。
Parallel Scavenge 年轻代 复制 JDK 8 默认 。关注吞吐量,支持自适应调整堆大小 。
CMS 老年代 标记-清除 关注低延迟。并发收集,但有碎片和浮动垃圾,JDK 9 后废弃 。
G1 整堆 复制 + 标记-整理 JDK 9 默认 。将堆划分为 Region,支持可控停顿时间,无内存碎片 。

5.4 分代回收机制

  • Young GC (Minor GC):Eden 区满时触发。存活对象由 Eden/From 区复制到 To 区,年龄 +1 。
  • 晋升机制:对象年龄达到阈值(默认 15)或 Survivor 区空间不足时,晋升到老年代 。
  • Full GC :老年代空间不足时触发,回收整个堆。若 Full GC 后仍不足,抛出 OutOfMemoryError
相关推荐
星释1 小时前
Rust 练习册 66:密码方块与文本加密
java·前端·rust
q***31891 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
心态特好1 小时前
JVM 如何判断‘对象 / 类该回收
jvm
专注于大数据技术栈1 小时前
java学习--==和equals
java·python·学习
鲸沉梦落1 小时前
JVM类加载
java·jvm
carry杰1 小时前
esayExcel导出图片
java·easyexcel 图片
路人甲ing..1 小时前
Android Studio 快速的制作一个可以在 手机上跑的app
android·java·linux·智能手机·android studio
心灵宝贝2 小时前
Mac 安装 JDK 8u281(JDK-8u281-1.dmg)详细步骤(附安装包)
java·macos·intellij-idea
記億揺晃着的那天2 小时前
从单体到微服务:如何拆分
java·微服务·ddd·devops·系统拆分