类加载
类生命周期
类的生命周期包括以下 7 个阶段:
加载(Loading)
验证(Verification)
准备(Preparation)
解析(Resolution)
初始化(Initialization)
使用(Using)
卸载(Unloading)
结束类生命周期的几种场景:
执行 System.exit()方法
程序正常执行结束
程序执行中遇到了异常或错误而异常终止
操作系统出现错误或强制结束程序而导致 JVM虚拟机进程终止
类加载过程
类加载过程包含:加载、验证、准备、解析、初始化,一共包括 5 个阶段。
加载
验证
准备
- 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是元空间区的内存。
- 实例变量不会在这阶段分配内存,它会在对象实例化时,随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
- 初始值一般为 0 值。
例一:
//比如下面这个 static 修饰的变量,在准备这一步,是给他分配空间,并赋予系统高要求的零值
// 在初始化时才给a 赋予我们要求的初始化值3
public static int a = 3;
例二:
//下面的 a 是我们定义的静态常量,它则被初始化为 3 而不是 0
public static final int a = 3;
解析
初始化
例如:以下代码中静态变量 i 只能赋值,不能访问,因为i 定义在静态代码块
的后面。
public test {
static{
i = 3; //给变量赋值可以正常编译通过
System .out.println(i); //这句编译器会提示“非法向前引用”
}
static int i = 1;
}
由于父类的 <clinit>()方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:
static class Parent{
Public static int A = 1;
static {
A = 2;
}
}
static class Son ectends Parent{
public static int B = A;
}
public static void main(String[] args){
system.out.println(Son.B); // 2
}
<clinit>线程安全
虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>()方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中,该阻塞非常隐蔽,几乎不会被察觉。
类的加载时机
主动引用
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了只有下列六种情况必须对类进行加载:
- 当遇到 new、getstatic、putstatic、或 invokestatic 这4条字节码指令时,比如 new 一个对象,读取一个静态字段(未被final修饰)、或调用一个类的静态方法时。
当 jvm 执行 new 指令时会加载类。即:当程序创建一个类的实例对象。
当 jvm 执行 getstatic 指令时会加载类。即:程序访问类的静态变量(不是静态常量,常量会
被加载到运行时常量池)。
当 jvm 执行 putstatic 指令时会加载类。即:程序给类的静态变量赋值。
当 jvm 执行 invokestatic 指令时会加载类。即:程序调用类的静态方法。
- 使用 java.lang.reflect 包的方法对类进行反射调用时如 Class.forname("."),或 newInstance()等等。如果类没初始化,需要触发类的加载。
- 加载一个类,如果其父类还未加载,则先触发该父类的加载。
- 当虚拟机启动时,用户需要定义一个要执行的主类(包含 main()方法的类),虚拟机会先加载这个类。
- 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了加载,则该接口要在实现类之前被加载。
被动引用
除主动引用之外,所有引用类的方式都不会触发加载成为被动引用。
被动引用的常见例子包括:
System.out.println(Subclass.value); // value 字段在 SubClass类的父类中定义
SuperClass [] sca = new SuperClass[10];
System.out.println(ConstClass.HELLOWORLD);
类加载器
类加载器
类加载器分类
public class ClassLoaderTest {
public static void main(String[] args) {
//(启动类)系统类加载器:
ClassLoader systemClassLoader = ClassLoader. getSystemclassLoader();
System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@73d16e93
//扩展类加载器:
ClassLoader extendClassLoader = systemClassLoader. getParent();
System.out.println(extendClassLoader); //sun.misc.Launcher$ExtClassLoader@15db9742
// 引导类加载器:
ClassLoader bootstrapClassLoader = extendClassLoader. getParent();
System.out.println(bootstrapClassLoader); // null
//用户自定义的类默认用系统类加载器
ClassLoader classLoader = ClassLoaderTest. class. getClassLoader();
System.out.println(classLoader); // sun.misc.Launcher$AppClassLoader@73d16e93
}
}
自定义类加载器使用场景
双亲委派模型
双亲委派机制工作原理
双亲委派的作用
例如: java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到ClassPath中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的Object比在ClassPath中的Object优先级更高,因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中使用的所有的 Object都是由启动类加载器所加载的 Object。
双亲委派额实现源码
以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出ClassNotFoundException,此时尝试自己去加载。
public abstract class classLoader {
// The parent class loader for delegation
private final ClassLoader parent;
public Class <? > loadClass(String name) throws ClassNotFoundException {
return loadclass(name, false);
}
protected Class <? > loadclass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class <? > c = findLoadedClass(name);
if (c == null) {
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class <? > findclass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}