1.JVM基础
1. Java 代码怎么跑起来的?
.java 文件编译成 .class 字节码文件,类加载器把 .class 加载进 JVM,字节码执行引擎从 main 方法开始执行。

2. 类加载的过程
一个类从加载到卸载,经历这几个阶段:
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
- 加载:代码中用到这个类时触发,把
.class文件读入内存 - 验证:检查字节码格式是否符合 JVM 规范
- 准备:给静态变量分配内存,赋默认初始值(
int赋 0,引用赋null) - 解析:把符号引用替换为直接引用(内存地址)
- 初始化:执行静态代码块和静态变量赋值,如果父类还没初始化,先初始化父类
3. 类加载器和双亲委派
JDK 内置三层类加载器:
Bootstrap ClassLoader:加载 JDK 核心类库(lib目录)Extension ClassLoader:加载扩展类库(lib/ext目录)Application ClassLoader:加载应用程序的类(ClassPath路径)
双亲委派:加载一个类时,先让父加载器去加载,父加载器加载不了再自己加载。

好处是保证核心类不会被覆盖,比如你自己写一个 java.lang.String,Bootstrap ClassLoader 会优先加载 JDK 的,你的永远不会生效。
3.1 Tomcat 是怎么打破双亲委派的?
Tomcat 需要同时部署多个 Web 应用,每个应用可能依赖同一个库的不同版本,比如 A 应用用 Spring 4,B 应用用 Spring 5。如果严格遵守双亲委派,两个版本的 Spring 类名完全一样,父加载器加载了一个版本后,另一个应用就没法加载自己的版本了。
Tomcat 的解决方案是为每个 Web 应用创建一个独立的 WebAppClassLoader,加载顺序改为:
1. 先从 JVM 核心类库找(Bootstrap,不能被覆盖)
2. 再从当前 Web 应用的 /WEB-INF/classes 和 /WEB-INF/lib 找
3. 找不到才委托给父加载器(Common ClassLoader)
第 2 步打破了双亲委派,不再优先委托父加载器,而是优先加载自己应用下的类。这样每个 Web 应用的 WebAppClassLoader 相互隔离,各自加载各自的依赖,互不干扰。
Bootstrap ClassLoader(JDK 核心类)
↑
Common ClassLoader(Tomcat 公共类,如 Servlet API)
↑
WebAppClassLoader(每个 Web 应用独立一个)
Common ClassLoader 加载的类所有应用共享(比如 Servlet API),WebAppClassLoader 加载的类只对当前应用可见,实现了应用间的类隔离。
4. JVM 内存区域
| 区域 | 线程共享 | 存放内容 |
|---|---|---|
| 堆 | 是 | 所有对象实例 |
| 方法区(MetaSpace) | 是 | 类信息、常量、静态变量 |
| 虚拟机栈 | 否(每线程一个) | 方法调用的局部变量、操作数栈 |
| 本地方法栈 | 否 | native 方法的局部变量 |
| 程序计数器 | 否 | 当前执行的字节码行号 |
| 堆外内存 | 是 | NIO DirectBuffer 分配的内存,见 JAVA的堆外内存 |
JDK 1.8 之前叫永久代(PermGen),1.8 之后改为 MetaSpace,用本地内存,不再受堆大小限制。详细的内存参数配置见 2.JVM内存划分。
5. GC 简介
GC 是后台线程,持续扫描堆内存,通过可达性分析判断对象是否存活:从 GC Roots(局部变量、静态变量、常量等)出发,能被引用链到达的对象存活,到达不了的就是垃圾。虚引用不影响对象存活,但可以监听回收事件,见 Java虚引用。