1.JVM基础

#jvm #java #类加载

1. Java 代码怎么跑起来的?

.java 文件编译成 .class 字节码文件,类加载器把 .class 加载进 JVM,字节码执行引擎从 main 方法开始执行。

java代码执行过程

2. 类加载的过程

一个类从加载到卸载,经历这几个阶段:

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

3. 类加载器和双亲委派

JDK 内置三层类加载器:

双亲委派:加载一个类时,先让父加载器去加载,父加载器加载不了再自己加载。

双亲委派

好处是保证核心类不会被覆盖,比如你自己写一个 java.lang.StringBootstrap 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虚引用

详细的 GC 算法和回收器见 3.垃圾回收,调优方法见 4.JVM调优