打卡学习JVM,第二天
本人学习过程中所整理的代码,源码地址
- 类加载器的类型
- Java虚拟机自带的加载器:根类加载器(Bootstrap),扩展类加载器(Extension),系统类加载器(System)
- 用户自定义的类加载器:java.lang.ClassLoader的子类,用户可以定制类的加载方式
Tips:类加载器并不需要等到某个类被“首次主动使用”时再加载它
JVM规范类允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError)
如果这个类一直没有程序被主动使用,那么类加载器就不会报告错误
- 类加载器的双亲委托机制
各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器;若有一个类加载器能够成功加载Test类,那么这个类加载器被称为“定义类加载器”,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为“初始类加载器”
若有一个类加载器能够成功加载Test类,那么这个类加载器就被称为定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器
//扩展类加载器的父类为根加载器,一般用null表示
public class MyTest12 {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
while (null!=classLoader){
classLoader = classLoader.getParent();
System.out.println(classLoader);
}
}
}
/*
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null
*/
数组类 Class 对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类加载器由Class.getClassLoader() 返回,该加载器与其元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。
public class MyTest14 {
public static void main(String[] args) {
String[] strings = new String[1];
System.out.println(strings.getClass().getClassLoader());
MyTest14[] myTest14s = new MyTest14[2];
System.out.println(myTest14s.getClass().getClassLoader());
int[] ints = new int[2];
System.out.println(ints.getClass().getClassLoader());
}
}
/*
null ->启动类加载器
sun.misc.Launcher$AppClassLoader@73d16e93
null ->没有类加载器
*/
- 双亲委托机制的优点
- 提高软件系统的安全性——用户自定义的类加载器不可能加载应该由父加载器加载的可靠类,从而防止不可靠代码代替由父加载器加载的可靠代码。例如String类总是由跟类加载器加载
- 类的初始化步骤
- 加入这个类还没有被加载和连接,那就先进行加载和连接
- 加入类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类
- 加入类中存在初始化语句,那就依次执行这些初始化语句
- 类的初始化时机
当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口
- 在初始化一个类时,并不会先初始化它所实现的接口
- 在初始化一个接口时,并不会先初始化它的父接口
因此一个父接口不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时才会导致该接口的初始化
public class MyTest {
public static void main(String[] args) {
System.out.println(MyChild1.b);
}
}
interface MyParent1 {
public static Thread thread = new Thread(){
{
System.out.println("MyParent1 invoked");
}
};
}
class MyChild1 implements MyParent1 {
public static int b = 5;
}
/*5*/
//调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,因此不会导致类的初始化
class CL{
static {
System.out.println("Class CL");
}
}
public class MyTest11 {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> clazz = loader.loadClass("classloader.CL");
System.out.println(clazz);
System.out.println("----------");
clazz = Class.forName("classloader.CL");
System.out.println(clazz);
}
}
/*
class classloader.CL
----------
Class CL
class classloader.CL
*/
通过前面所有代码的验证,基本可以确定类的主动使用会导致类的初始化,因此类的初始化时机总结如下,总共为7中情况
- 创建类的实例
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如Class.forName(“类的全限定名”))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类
- JDK1.7开始提供的动态语言支持
**注意:调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
总结:
今天主要对类加载的双亲委托机制以及类初始化的时机进行了探讨。