GC日志解读:看懂每一行GC输出

GC日志参数 基础参数 # JDK 8及之前 java -XX:+PrintGC \ -XX:+PrintGCDetails \ -XX:+PrintGCTimeStamps \ -XX:+PrintGCDateStamps \ -Xloggc:gc.log \ -jar app.jar # JDK 9+统一日志 java -Xlog:gc*:file=gc.log:time,uptime,level,tags \ -jar app.jar 常用参数 参数 说明 JDK版本 -XX:+PrintGC 打印GC简要信息 8及之前 -XX:+PrintGCDetails 打印GC详细信息 8及之前 -XX:+PrintGCTimeStamps 打印GC时间戳 8及之前 -Xloggc:gc.log GC日志输出到文件 8及之前 -Xlog:gc* 统一GC日志 9+ 日志格式解读 Minor GC日志(JDK 8) 2023-01-15T10:30:45.123+0800: 0.234: [GC (Allocation Failure) [PSYoungGen: 6144K->640K(7168K)] 6585K->2770K(23552K), 0.0019356 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 逐项解读: 部分 含义 2023-01-15T10:30:45.123 GC发生时间 0.234 JVM启动后的时间(秒) GC Minor GC Allocation Failure 触发原因:内存分配失败 PSYoungGen Parallel Scavenge新生代收集器 6144K->640K 新生代从6MB降到640KB (7168K) 新生代总大小7MB 6585K->2770K 整堆从6.4MB降到2.7MB (23552K) 堆总大小23MB 0.0019356 secs GC耗时1.9毫秒 user=0.01 用户态CPU时间 sys=0.00 内核态CPU时间 real=0.00 实际耗时 Full GC日志 [Full GC (Ergonomics) [PSYoungGen: 808K->0K(9216K)] [ParOldGen: 6144K->6827K(10240K)] 6952K->6827K(19456K), [Metaspace: 3072K->3072K(1056768K)], 0.0098765 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 关键点: ...

2025-11-20 · maneng

打破双亲委派:自定义类加载器实战

引言 为什么要学习这个主题? 在前面的文章中,我们了解了双亲委派模型及其保护机制。但实际开发中,有些场景需要突破这些限制: Tomcat如何实现多个应用使用不同版本的同一个库? 如何实现代码的热部署(不重启更新代码)? 如何加密class文件防止反编译? 这些需求都需要自定义类加载器。理解自定义类加载器,就能掌握Java类加载的灵活性和扩展性。 你将学到什么? ✅ 如何自定义类加载器 ✅ 如何打破双亲委派模型 ✅ 实现热部署功能 ✅ 实现类文件加密 ✅ Tomcat的类加载架构 ✅ 自定义类加载器的最佳实践 一、自定义类加载器的基础 1.1 继承ClassLoader 自定义类加载器只需要继承ClassLoader并重写findClass方法。 public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 1. 根据类名找到.class文件 // 2. 读取字节码数据 // 3. 调用defineClass转换为Class对象 return super.findClass(name); } } 1.2 关键方法 方法 作用 是否重写 loadClass() 加载类的入口 通常不重写(保持双亲委派) findClass() 查找并加载类 必须重写 defineClass() 字节码 → Class对象 不要重写(final方法) resolveClass() 解析类 可选重写 重要: 重写findClass():遵循双亲委派 重写loadClass():打破双亲委派 二、实战1:从指定目录加载类 2.1 需求 从/custom/classes/目录加载类,而不是从classpath。 ...

2025-11-20 · maneng

双亲委派模型:为什么要这样设计?

引言 为什么要学习这个主题? 在上一篇文章中,我们了解了JVM的三层类加载器体系。但你是否想过: 为什么要设计"父子"关系?直接加载不行吗? 如果我自己写一个java.lang.String类会发生什么? 双亲委派模型是如何保证Java核心类库不被篡改的? 双亲委派模型是类加载机制的核心,也是JVM安全性的基础。理解它,就能理解Java为什么是一个安全的平台。 你将学到什么? ✅ 双亲委派模型的完整工作流程 ✅ 为什么要设计双亲委派模型 ✅ 双亲委派模型带来的好处 ✅ 如何验证双亲委派模型 ✅ 双亲委派模型的局限性 一、什么是双亲委派模型? 1.1 定义 当一个类加载器收到类加载请求时,它首先不会自己尝试加载,而是把这个请求委派给父加载器完成。只有当父加载器无法完成加载时,子加载器才会尝试自己加载。 1.2 工作流程 应用类加载器收到加载请求:加载 com.example.User ↓ 委派给父加载器 扩展类加载器尝试加载 com.example.User ↓ 委派给父加载器 启动类加载器尝试加载 com.example.User ↓ 在核心类库中找不到 ✗ 返回"无法加载" ↓ 扩展类加载器尝试自己加载 ↓ 在扩展库中找不到 ✗ 返回"无法加载" ↓ 应用类加载器尝试自己加载 ↓ 在classpath中找到 ✓ 加载成功 1.3 流程图 ┌────────────────────────────────────────────┐ │ 1. 应用类加载器收到请求 │ │ "加载 com.example.User" │ └──────────────┬─────────────────────────────┘ │ 委派给父加载器 ┌──────────────▼─────────────────────────────┐ │ 2. 扩展类加载器收到请求 │ │ 检查:是否在 lib/ext 中? │ └──────────────┬─────────────────────────────┘ │ 委派给父加载器 ┌──────────────▼─────────────────────────────┐ │ 3. 启动类加载器收到请求 │ │ 检查:是否在 rt.jar 中? │ │ 结果:✗ 没找到 │ └──────────────┬─────────────────────────────┘ │ 返回失败 ┌──────────────▼─────────────────────────────┐ │ 4. 扩展类加载器尝试加载 │ │ 在 lib/ext 中查找 │ │ 结果:✗ 没找到 │ └──────────────┬─────────────────────────────┘ │ 返回失败 ┌──────────────▼─────────────────────────────┐ │ 5. 应用类加载器尝试加载 │ │ 在 classpath 中查找 │ │ 结果:✓ 找到并加载 │ └────────────────────────────────────────────┘ 二、双亲委派模型的源码实现 2.1 ClassLoader.loadClass() 源码 public abstract class ClassLoader { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 首先检查该类是否已经被加载 Class<?> c = findLoadedClass(name); if (c == null) { try { // 2. 如果有父加载器,委派给父加载器加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 3. 如果没有父加载器,委派给启动类加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 父加载器无法加载,不做处理 } if (c == null) { // 4. 父加载器无法加载时,调用自己的findClass c = findClass(name); } } // 5. 如果需要,解析类 if (resolve) { resolveClass(c); } return c; } } // 子类需要重写这个方法 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } } 2.2 关键步骤解析 步骤1:检查缓存 ...

2025-11-20 · maneng

类加载器家族:启动、扩展、应用类加载器

引言 为什么要学习这个主题? 在上一篇文章中,我们了解了类加载的完整生命周期。但你是否想过: JVM中有多少个类加载器? 为什么要设计多个类加载器? java.lang.String和我们自己写的类是由同一个加载器加载的吗? 理解类加载器的层次结构,就像理解一个组织的部门分工。不同的类加载器负责加载不同来源的类,这种设计既保证了安全性,又提供了灵活性。 你将学到什么? ✅ JVM的三层类加载器体系 ✅ 每个类加载器的职责和加载路径 ✅ 类加载器的父子关系(委派关系) ✅ 如何查看类是由哪个加载器加载的 ✅ 线程上下文类加载器 一、类加载器的层次结构 1.1 三层体系 ┌────────────────────────────────────────┐ │ 启动类加载器 │ Bootstrap ClassLoader │ (Bootstrap ClassLoader) │ C++实现,加载核心类库 │ 加载:rt.jar、核心API │ └──────────────┬─────────────────────────┘ │ 父加载器 ┌──────────────▼─────────────────────────┐ │ 扩展类加载器 │ Extension ClassLoader │ (Extension ClassLoader) │ Java实现,加载扩展库 │ 加载:lib/ext/*.jar │ └──────────────┬─────────────────────────┘ │ 父加载器 ┌──────────────▼─────────────────────────┐ │ 应用类加载器 │ Application ClassLoader │ (Application ClassLoader) │ Java实现,加载应用类 │ 加载:classpath下的类 │ └────────────────────────────────────────┘ │ 父加载器(可自定义) ┌──────────────▼─────────────────────────┐ │ 自定义类加载器 │ User ClassLoader │ (Custom ClassLoader) │ 继承ClassLoader │ 加载:自定义路径的类 │ └────────────────────────────────────────┘ 关键理解: ...

2025-11-20 · maneng

类加载的完整生命周期:7个阶段详解

引言 为什么要学习这个主题? 在第一阶段我们了解了JVM的基础架构,知道类加载子系统是JVM的重要组成部分。但你是否想过: 一个.class文件是如何一步步变成可用的Java类的? 为什么有时候类的静态变量有默认值,有时候又是我们设置的值? 类在什么时候被加载?什么时候被卸载? 理解类加载的生命周期,就像理解一个产品从原材料到成品的生产流程。这是理解Java类初始化、解决类加载问题的关键。 你将学到什么? ✅ 类加载的7个阶段及其作用 ✅ 每个阶段的详细执行过程 ✅ 验证、准备、初始化的区别 ✅ 类的卸载机制 ✅ 类加载的时机和触发条件 一、类加载的完整生命周期 1.1 七个阶段概览 .class文件 ↓ 1️⃣ 加载 (Loading) ↓ 2️⃣ 验证 (Verification) ↓ 3️⃣ 准备 (Preparation) } 链接 (Linking) ↓ 4️⃣ 解析 (Resolution) ↓ 5️⃣ 初始化 (Initialization) ↓ 6️⃣ 使用 (Using) ↓ 7️⃣ 卸载 (Unloading) 时间线: 加载 → 验证 → 准备 → 解析 → 初始化 └──────────┬──────────┘ 链接阶段 关键理解: 前5个阶段的顺序是固定的(但可以交叉进行) 解析阶段可能在初始化之后(动态绑定) 验证、准备、解析统称为"链接"阶段 二、阶段1:加载(Loading) 2.1 作用 将.class文件的二进制数据读入内存,转换成方法区的运行时数据结构,并在堆中创建一个java.lang.Class对象。 ...

2025-11-20 · maneng

JVM、JRE、JDK三者的关系与区别

引言 为什么要学习这个主题? 当你第一次学习Java时,可能遇到过这些困惑: 下载Java时,看到JDK、JRE、JVM,该选哪个? 为什么有人说"装JDK就够了"? JDK和JRE有什么区别?为什么JDK更大? 这些问题看似简单,但很多开发者对三者的关系理解模糊。就像区分"汽车"、“发动机”、“汽车制造工厂"一样,理解它们的区别对配置开发环境至关重要。 你将学到什么? ✅ JVM、JRE、JDK的准确定义 ✅ 三者的包含关系和依赖关系 ✅ 什么时候需要JDK,什么时候只需JRE ✅ 如何选择和配置Java环境 ✅ OpenJDK vs Oracle JDK的区别 一、核心概念定义 1.1 JVM(Java Virtual Machine) 定义:Java虚拟机,负责执行字节码的运行时环境。 职责: 加载和执行字节码 管理内存(堆、栈等) 垃圾回收 提供安全机制 类比:发动机 提供运行动力 是核心执行部件 但单独无法工作 独立使用:❌ 不能单独使用 JVM需要配合类库才能运行Java程序 1.2 JRE(Java Runtime Environment) 定义:Java运行时环境,包含JVM和Java核心类库。 组成: JRE = JVM + Java核心类库 + 支持文件 包含内容: JVM(如HotSpot) Java标准库(rt.jar等) 配置文件 属性设置 职责: 运行已编译的Java程序(.class文件) 类比:汽车(完整的运行系统) 有发动机(JVM) 有座椅、方向盘(类库) 可以正常行驶(运行程序) 独立使用:✅ 可以 如果只需要运行Java程序(不开发),只装JRE即可 1.3 JDK(Java Development Kit) 定义:Java开发工具包,包含JRE和开发工具。 ...

2025-11-20 · maneng

字节码是什么?从.java到.class的编译过程

引言 为什么要学习这个主题? 在前面的文章中,我们多次提到"字节码"这个概念。但字节码到底是什么? 想象一下: 为什么.class文件比.java文件还大? javac编译器到底做了什么? 字节码长什么样子?能看懂吗? 理解字节码是理解JVM的关键。就像理解汇编语言能帮助理解CPU一样,理解字节码能帮助我们: 优化代码性能 理解JVM的执行机制 排查诡异的BUG 实现字节码增强(如AOP) 你将学到什么? ✅ 字节码的本质和作用 ✅ javac编译器的工作流程 ✅ class文件的格式结构 ✅ 如何查看和理解字节码 ✅ 常见的字节码指令 一、什么是字节码? 1.1 字节码的定义 字节码(Bytecode):一种介于源代码和机器码之间的中间表示形式。 源代码(人类可读) ↓ javac编译 字节码(JVM可读) ↓ JVM解释/编译 机器码(CPU可读) 特点: 不是源代码,不是机器码 平台无关(跨平台的关键) 紧凑高效(比源代码小) 包含完整的类型信息 1.2 为什么需要字节码? 方案1:直接编译成机器码(C/C++方式) .java → [编译器] → .exe (Windows机器码) → .out (Linux机器码) → .app (macOS机器码) 缺点: 需要为每个平台单独编译 无法跨平台运行 无法在运行时优化 方案2:直接解释执行源代码(Python方式) .java → [解释器] → 逐行解释执行 缺点: 执行效率低 无法进行静态类型检查 无法提前优化 方案3:字节码中间层(Java方式)✅ .java → [javac] → .class (字节码) ↓ [JVM解释/JIT] → 机器码 优点: ...

2025-11-20 · maneng

JVM架构全景图:五大核心组件详解

引言 为什么要学习这个主题? 在前两篇文章中,我们知道了Java程序如何运行,以及JVM的本质。但JVM内部到底是怎么工作的? 想象一下: 一个.class文件是如何被加载到JVM中的? 对象和变量存储在哪里? 字节码是如何被"翻译"成机器码的? 理解JVM的架构,就像理解一台计算机的组成(CPU、内存、硬盘)一样重要。这是后续学习类加载、内存管理、GC调优的基础。 你将学到什么? ✅ JVM的整体架构图 ✅ 五大核心组件的职责 ✅ 各组件如何协作运行Java程序 ✅ JVM的完整执行流程 一、JVM架构全景图 1.1 整体架构 ┌─────────────────────────────────────────────────────────────┐ │ Java应用程序 │ │ (.java → .class) │ └──────────────────────┬──────────────────────────────────────┘ │ 字节码 ↓ ┌─────────────────────────────────────────────────────────────┐ │ JVM(Java虚拟机) │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 1️⃣ 类加载子系统 (Class Loader Subsystem) │ │ │ │ - 加载 (Loading) │ │ │ │ - 链接 (Linking): 验证、准备、解析 │ │ │ │ - 初始化 (Initialization) │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 2️⃣ 运行时数据区 (Runtime Data Areas) │ │ │ │ │ │ │ │ 【线程共享】 【线程私有】 │ │ │ │ - 堆 (Heap) - 程序计数器 (PC Register)│ │ │ │ - 方法区 (Method Area) - 虚拟机栈 (VM Stack) │ │ │ │ - 本地方法栈 (Native Stack)│ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 3️⃣ 执行引擎 (Execution Engine) │ │ │ │ - 解释器 (Interpreter) │ │ │ │ - JIT编译器 (Just-In-Time Compiler) │ │ │ │ - 垃圾收集器 (Garbage Collector) │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 4️⃣ 本地接口 (Native Interface - JNI) │ │ │ └────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 5️⃣ 本地方法库 (Native Method Libraries) │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 操作系统 & 硬件 │ └─────────────────────────────────────────────────────────────┘ 1.2 核心组件概览 组件 作用 类比 类加载子系统 加载、链接、初始化类 快递员(把包裹送到仓库) 运行时数据区 存储数据(对象、变量、代码) 内存/仓库 执行引擎 执行字节码 CPU(执行指令) 本地接口 调用操作系统API 操作系统接口 垃圾收集器 自动回收无用对象 清洁工(清理垃圾) 二、类加载子系统(Class Loader Subsystem) 2.1 职责 负责将.class文件加载到JVM内存中,并准备好供使用。 ...

2025-11-20 · maneng

JVM到底是什么?虚拟机的本质

引言 为什么要学习这个主题? 在上一篇文章中,我们了解了Java程序的运行流程,知道了JVM在其中扮演着"翻译官"的角色。但你是否想过: JVM到底是一个软件?还是一个标准? 为什么Oracle的JDK和OpenJDK的行为基本一致? HotSpot、OpenJ9、GraalVM这些名字都是什么?有什么区别? 理解JVM的本质,是掌握Java技术栈的关键。就像理解"什么是HTTP"比单纯会用浏览器更重要一样。 你将学到什么? ✅ 虚拟机的本质与分类 ✅ JVM规范与JVM实现的区别 ✅ 主流JVM实现的特点与选择 ✅ 如何查看和切换JVM 一、什么是虚拟机? 1.1 虚拟机的定义 虚拟机(Virtual Machine):通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。 简单来说:用软件模拟硬件。 1.2 虚拟机的分类 系统虚拟机(System VM) 代表:VMware、VirtualBox、Parallels Desktop 特点: 模拟完整的计算机系统 可以运行完整的操作系统 提供硬件虚拟化(CPU、内存、硬盘、网卡等) 使用场景: 物理机(Windows) ↓ VMware 虚拟机 ↓ 虚拟Linux系统(完整的操作系统) 进程虚拟机(Process VM) 代表:JVM、Python解释器、.NET CLR 特点: 只模拟程序运行环境 不需要完整的操作系统 为单个进程提供跨平台的执行环境 使用场景: 物理机(任何OS) ↓ JVM进程虚拟机 ↓ Java程序(字节码) 1.3 JVM属于哪一类? JVM是进程虚拟机(Process VM) 不模拟硬件 只提供字节码的执行环境 每个Java程序运行在一个JVM进程中 关键理解: 系统虚拟机 = 虚拟计算机(有CPU、内存、硬盘) 进程虚拟机 = 虚拟执行环境(只能运行特定程序) 二、JVM的本质:规范 vs 实现 2.1 JVM规范(JVM Specification) 定义:Oracle官方发布的一份技术文档,详细定义了JVM应该做什么、怎么做。 ...

2025-11-20 · maneng

Java程序是如何运行的?从HelloWorld说起

引言 为什么要学习这个主题? 你是否曾经好奇: 为什么Java程序能够"一次编写,到处运行"? 一个简单的 System.out.println("Hello World") 背后发生了什么? .java 文件和 .class 文件有什么区别? 作为JVM学习的第一课,理解Java程序的运行机制是后续所有知识的基础。就像学习汽车驾驶前需要了解汽车的基本构造一样,学习JVM调优前也需要先理解Java程序是如何运行的。 你将学到什么? ✅ Java程序从源代码到执行的完整流程 ✅ JVM在Java运行机制中的核心作用 ✅ “一次编写,到处运行"的底层原理 ✅ 编译型语言 vs 解释型语言,Java是什么? 一、从HelloWorld开始 1.1 最简单的Java程序 // HelloWorld.java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } 执行这个程序需要两个步骤: # 第一步:编译 javac HelloWorld.java # 第二步:运行 java HelloWorld 输出: Hello, World! 看起来很简单,但这两行命令背后隐藏着Java运行机制的全部秘密。 1.2 编译后发生了什么? 执行 javac HelloWorld.java 后,目录中会生成一个新文件: HelloWorld.java # 源代码(Java语言) HelloWorld.class # 字节码(JVM语言) 关键问题:为什么不是直接生成机器码(如 .exe 文件)? 这就是Java与C/C++等传统编译型语言的核心区别。 二、Java程序的两阶段运行模型 2.1 传统编译型语言(如C语言) 源代码(.c) → [C编译器] → 机器码(.exe) → [CPU直接执行] 特点: ...

2025-11-20 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...