4. 类加载阶段
4.1 加载
- 将类的字节码载入方法区中,内部采用C++的instanceKlass描述java类,它的重要field有:
- _java_mirror即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
- _super 即父类
- _methods 即方法
- constants 即常量池
- class_loader 即类加载器
- vtable 虚方法表
- _itable 接口放发表
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
注意
- instanceKlass 这样的【元数据】是存储在方法区(1.8后的元空间内)但 _java_mirror 是存储在堆中
- 可以通过HSDB工具查看
4.2 链接
- 验证:验证类是否符合 JVM 规范,安全性检查
- 用 UE 等支持二进制的编译器修改 HelloWorld.class 的魔数,在控制台运行
PS D:\view\out\production\view> java HelloWorld
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: HelloWorld has been compiled by a more recent version of the Java Runtime (class file version 8244.8224), this version of the Java Runtime only recognizes class file versions up to 52.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
- **准备:**为 static 变量分配空间,设置默认值
- static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
- static 变量分配空间和赋值时两个步骤,分配空间准备阶段完成,赋值在初始化阶段完成
- 如果 static 变量时 final 的基本类型以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
- 如果 static 变量时 final 的,但属于引用类型,那么赋值也会在初始化阶段完成
- 解析:将常量池中的符号引用解析为直接引用
/**
* 解析的含义
*/
public class Load1 {
public static void main(String[] s) throws ClassNotFoundException {
ClassLoader classLoader = Load1.class.getClassLoader();
// loadClass 方法不会导致类的解析和初始化
// 只会进行对类 C 的加载,并不会导致类 C 的解析以及初始化,从而类D也不会被加载、解析、初始化
Class<?> c = classLoader.loadClass("C");
// 这个就会C D 进行加载、解析、初始化
//new C();
}
}
class C {
D d = new D();
}
class D {
}
4.3 初始化
<cinit>()v 方法
- 初始化调用 <cinit>()v ,虚拟机会保证这个类的【构造方法】的线程安全
发生时机
- 概括得说,类初始化时【懒惰的】
- mian 方法所在的类,总会被首先初始化
- 首次访问这个类的静态变量或静态方法时
- 子类初始化,如果父类还没有初始化,会引发
- 子类访问父类的静态方法,只会触发父类的初始化
- Class.forName
- new 会导致初始化
- 不会导致初始化的情况
- 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
- 类对象 .class不会触发初始化
- 创建该类的数组不会触发初始化
类加载器的 loadClass 方法- Class.forName 的参数2为false时
- 代码例子(一下代码需要自己一行一行运行)
public class Load2 {
static {
System.out.println("main init");
}
public static void main(String[] args) throws ClassNotFoundException {
//================不会初始化=======================//
// 1.静态常量不会触发初始化
//System.out.println(B.b);
// 2.类对象 .class 不会触发初始化
//System.out.println(B.class);
// 3.创建该类的数组不会触发初始化
//System.out.println(new B[0]);
// 4.不会初始化类B,但是会加载B、A
/*ClassLoader c1 = Thread.currentThread().getContextClassLoader();
c1.loadClass("B");*/
// 5. 不会初始化类B,但是会加载B、A
/*ClassLoader c2 = Thread.currentThread().getContextClassLoader();
Class.forName("B", false, c2);*/
//================会初始化=======================//
// 1.首次访问这个类的静态变量或静态方法
//System.out.println(A.a);
// 2.子类初始化,如果父类还没有初始化,会引发
//System.out.println(B.c);
// 3.子类访问父类静态变量,只会触发父类初始化
//System.out.println(B.a);
// 4.会初始化类B,并先初始化类A
//Class.forName("B");
}
}
class A {
static int a = 0;
static {
System.out.println("a init");
}
}
class B extends A{
final static double b = 5.0;
static boolean c = false;
static {
System.out.println("b init");
}
}
4.4 练习
- 从字节码角度分析,使用 a,b,c 三个常量是否会导致 E 的初始化
public class Load3 {
public static void main(String[] args) {
// 不会
System.out.println(E.a);
// 不会
System.out.println(E.b);
// 会
System.out.println(E.c);
}
}
class E {
static {
System.out.println("init E");
}
public static final int a = 10;
public static final String b = "hello";
public static final Integer c = 20;
}
===============E.class字节码=========================
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String init E
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: bipush 20
10: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: putstatic #6 // Field c:Ljava/lang/Integer;
16: return
- 典型应用 - 完成懒惰初始化单例模式
public class Singleton {
private Singleton(){};
// 内部类中保存单例
private static class LazyHolder{
static final Singleton INSTANCE = new Singleton();
}
// 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态变量
public static Singleton getInstance(){
return LazyHolder.INSTANCE;
}
}
================特点===================================
1. 懒惰实例化
2. 初始时的线程安全是有保障的