JVM类加载 | 8lovelife's life
0%

JVM类加载

回顾回顾JVM中的类加载

类的生命周期

类的生命周期包括类的加载、链接、初始化、使用、销毁
image

类装入JVM

将类装入JVM供使用的过程分为:类的加载、链接、初始化

  • 显示装入:1。 调用类加载器中的loadClass方法 2。调用Class.forName方法
  • 隐示装入:解析类中引用的其他类的时候,所引用的类未被加载时则被隐示装入

类的加载

类的加载包括

  1. 将.class文件加载到JVM方法区中
  2. 在堆中生成Class对象,Class对象提供了访问方法区中类数据结构的接口

    这一阶段类对象仅有基本的内存结构,类对象中的方法、字段、引用都不做处理,此时的类还不能使用

类的链接

类的链接将内存中二进制数据转换到JVM运行数据区,链接包括验证、准备、解析

  • 验证:验证类的字节码是否合法
  • 准备:为类的静态变量分配内存并设置默认值
1
2
final static int a = 10  准备阶段阶段 a 的默认值为 10
static int b = 10 准备阶段 b 的默认值为 0
  • 解析:将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行

类的初始化

类的初始化是执行<clinit>()方法的过程

  • <clinit>() 由编译器按照代码书写的顺序将类中的 静态变量的赋值与静态代码块 合并到此方法中
  • <clint>() 同时只能有一个线程且只能执行一次,利用这个特性可以写一个安全的懒加载单例类

类的初始化时机,若类未初始化则以下动作将进行类的初始化

  1. new对象、调用静态方法、获取非final修饰的静态变量、设置静态变量
  2. 对类进行反射调用
  3. 类的初始化发生时,若有父类则先执行父类的类的初始化
  4. 虚拟机会对启动类(即含有main方法的类)进行类的初始化

类的使用

实例化、使用对象方法等

类的卸载

Class对象被回收、Class方法区中数据结构被卸载

类加载器

ClassLoader用于将类加载在JVM中供使用

ClassLoader工作机制

类的加载采用双亲委派模式

image

ClassLoader工作过程

  1. 缓存(是否已加载
  2. 委托父加载器
  3. 自己加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
抽象类ClassLoader核心方法:

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 若类并未被加载则委托父加载器加载
c = parent.loadClass(name, false);
} else {
// 若没有父加载器则委托给启动加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 当前类加载器开始加载类
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c); //加载的类是否进行解析
}
return c;
}
}



protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
  • findClass方法由不同的加载器实现定制(网络加载.class文件等
  • defineClass方法用与创建Class对象

双亲委派模式

ClassLoader双亲委派模式的能与不能

双亲委派加载的好处

  1. 保证了Java核心类库的安全
  2. JVM中能够支持隔离的类空间(不同加载器加载同一个class)

SPI

SPI(Service Provider Interface)是Java提供的位于核心类库中的接口,由第三方实现。如JDBC、JNDI等。按照ClassLoader的双亲委派模式,最终加载SPI的是Bootstrap ClassLoader,而SPI的实现类是由System ClassLoader加载的,最终只会加载失败。

  • 针对SPI的接口Java使用线程上下文加载器(Thread.currentThread().getContextClassLoader())来显式加载SPI的实现

Justice is not a product of the court. It’s a product of the people.

12 Angry Men