# Class从加载到初始化

# 执行步骤

image-20201109205534906

# Loading (加载)

类加载的过程是由类加载器通过双亲委派机制加载.

# Linking (连接)

# verification (验证)

验证文件是否符合JVM规定

# preparation (准备)

静态成员变量赋默认值

例如 int 0; long 0;

# resolution (解析)

将类、方法、属性等符号引用解析为直接引用, 很多解析都是动态引用 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

# Initializing (初始化)

调用类初始化代码 <clinit>,给静态成员变量赋初始值

# 相关面试题

public static void main(String[] args) {
  System.out.println(T.count);
}

class T {
    public static T t = new T();
    public static int count = 2;

  	private T() {
        count ++;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

输出结果

2
1

执行过程为, 加载 -> ... -> 制备 -> 初始化

制备的时候, 会给成员变量设置初始值 t 的初始值是 null, count 的初始值是 0

初始化的时候赋初始值 t = new T() , count = 2所以输出结果是 2

如果 6 / 7 两个语句块调换位置的话, 输出值是 3

# 赋值过程总结

  1. load - 默认值 - 初始值
  2. new - 申请内存 - 默认值 - 初始值

# 类加载器

类加载器(ClassLoader) JVM 有不同的类加载器, 用于加载各种类.

父类加载器不是"类加载器的加载器"

也不是父类加载器

Bootstrap, Extension, App 都是 Launcher 的静态内部类, 他们各自的加载路径就是定义在 Launcher 中的.

# 作用

JVM 中所有的类都是通过类加载器加载到内存的.

当一个 Class 被 ClassLoader 加载到内存后,

  1. 开辟一块内存空间, 用于存储 Class 的相关信息
  2. 生成一个 Class 对象, 这个对象指向 1 中的内存空间.
  3. 其他对象使用此 Class 时, 是通过 2 中的 Class 获取的, 而不是直接访问内存中的信息.

# 特性

JVM 是按需动态加载的, 加载时采用双亲委派机制

注意: 各个 Class Loader 之间没有继承关系

# Bootstrap Class Loader

根类加载器用来加载 JDK 中的最核心的类, lib/rt.jar charset.jar 等

是由 C++ 实现的.

lib/rt.jar 中包括 String.class Object.class 等等,

通过 Class.getClassLoader() 方法获取, 得到的结果是 null

# Extension Class Loader

扩展加载器用于加载扩展包的相关文件 jar/lib/ext/*.jar

也可以通过 -Djava.ext.dirs 指定

例如 com.sun.java.accessibility.util.EventQueueMonitor, sun.net.spi.nameservice.dns.DNSNameService 等类

# APP Class Loader

加载 classpath 指定的所有 jar 或目录

# Custom Class Loader

自定义的 ClassLoader

# 如何自定义类加载器

加载一个类, 只需要调用 ClassLoader 的 loadClass() 方法即可.

Class clazz = T005_LoadClassByHand.class.getClassLoader()
                .loadClass("com.mashibing.jvm.c2_classloader.T002_ClassLoaderLevel");
1
2

用途: 热启动, 热部署

# 实现过程
  1. 继承 ClassLoader
  2. 实现 findClass() 方法

ClassLoader 使用的设计模式是: 模板模式

自定义 ClassLoader 时, parent 是什么时候指定的?

ClassLoader 在初始化时会默认指定, 也可以在自定义的 ClassLoader 初始化时自己指定.

# 双亲委派机制

一个 Class 加载到内存的过程

Custom(没有) --委派--> App(没有) -- 委派 --> Extension(没有) --委派--> Bootstrap(没有)

--委托--> Extension(不归我管) --委托--> APP(不归我管) --委托--> Custom(加载)

如果 Custom 能正常加载, 则返回, 如果找不到, 则抛出 ClassNotFoundException

image-20201109224658734

每个加载器都有自己的缓存

类加载器加载的源码如下

synchronized (getClassLoadingLock(name)) {
  // First, check if the class has already been loaded
  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;
}
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

# 为什么要做双亲委派机制

  • 主要是为了安全

    如果通过自己的 ClassLoader 能随意加载各种类, 比如加载自定义的 java.lang.String 就可以自己在其中加脚本.

  • 避免资源浪费

# Lazyloading

  • 懒加载, 严格来讲应该叫 lazyInitializing(懒初始化)
  • JVM 规范并没有规定什么时候加载
  • 但是严格的规定了什么时候必须初始化
    • new, getstatic, putstatic, invokestatic 指令, 访问 final 变量除外
    • java.lang.reflect 对类进行反射调用时
    • 初始化子类的时候, 父类首先初始化
    • 虚拟机启动时, 被执行的类必须初始化
    • 动态语言支持 java.lang.invoke.MethodHandle 解析的结果为 REF_getstatic, REF_putstatic, REF_invokestatic 的方法句柄时, 该类必须初始化.

# 执行模式

Java 代码执行的时候到底是解释执行还是编译执行?

默认情况下是混合执行的.

# 混合模式

  • 混合使用解释器(bytecode interpreter) + 热点代码编译(JIT Just In-Time compiler)
  • 起始阶段采用解释执行
  • 热点代码检查, 满足条件后执行编译
    • 多次被调用的方法(方法计数器: 监测方法执行频率)
    • 多次被调用的循环(循环计数器: 监测循环执行频率)

可以通过启动时参数设定其执行模式

  • -Xmixed 混合模式(默认) 开始解释执行, 启动速度较快, 对热点代码实行监测和编译
  • -Xint 使用解释模式 启动速度很快, 执行稍慢
  • -Xcomp 使用纯编译模式, 执行很快, 启动很慢

检测热点代码:-XX:CompileThreshold = 10000

上次更新时间: 2020/11/11 上午12:55:02