一、详解类加载机制
1.类加载机制:将javac编译后生成的Class字节码文件(一串二进制的字节流)加载到内存中,并对数据进行验证准备解析初始化,然后在堆区创建一个java.lang.Class对象,可被JVM直接使用。
2.类的生命周期:加载 ---> 验证-->准备-->解析 ---> 初始化 ---> 使用 ---> 卸载
3.加载:通过类加载器,将class字节码文件加载到内存。(class字节码文件可来自各种途径)
加载过程: a.通过类的全名获取对应的二进制字节流; b.将二进制字节流的静态存储结构转化为方法区的运行时数据结构; c.在堆中生成一个java.lang.Class对象。
结果:方法区内多了类的二进制数据,堆中增加一个Class对象(反射对象)。
4.链接:将二进制字节码合并到JVM的运行状态中。
验证:确保字节码信息符合JVM规范,保证安全;
准备:为static变量分配内存、附默认值(不会直接赋值),给常量直接赋值;
解析:将各种符号引用替换为直接引用。1.类或接口的解析. 2.字段解析. 3.类方法解析. 4.接口方法解析.
5.初始化:执行类构造器<clinit>()方法的过程。类构造器由static变量和static代码块组成。初始化的本质就是执行了静态变量和静态代码块;初始化子类时,先执行父类的类构造器。 对象的初始化,是先初始化成员变量,再执行构造方法。
6.类的主动引用和被动引用:是否进行类初始化。
主动引用: new对象; 反射调用; 调用静态属性/方法(final除外); main()所在的类; 子类被调时先初始化父类
被动引用: 子类引用父类静态数据,子类不初始化; MyObj[] arr = new MyObj[1];数组定义某个类,不初始化; 引用final常量不初始化(链接中的准备/解析两步,已经把常量存到常量池中并直接引用)
7.初始化代码执行顺序: 1.父类静态方法/静态代码块(跟代码顺序一致) 2.子类static 3.父类成员变量初始化,再构造函数 4.子类成员变量初始化,再构造函数。
8.使用:对类进行实例化。 卸载:该类的所有实例对象都不被引用时,类在方法区中的数据就会被卸载(类的卸载取决于它的实例能活多久).
二、有哪些类加载器,分别加载哪些文件
1.引导类加载器(Bootstrap ClassLoader):C/C++实现,加载原生代码 JAVA_HOME/jre/lib/rt.jar (java基础类库)
2.扩展类加载器(Extention ClassLoader):加载 JAVA_HOME/jre/ext/*.jar
3.应用程序类加载器(APP ClassLoader):根据应用的类路径(classpath)加载,一般java应用类都由它加载,是默认的classLoader。
4.自定义类加载器:继承java.lang.ClassLoader,加载指定类。一般覆盖findClass()方法,而不要直接改写loadClass()方法。自定义的CL一般用来加载其他途径的class字节码文件,默认parent是APP CL。
5.java.lang.ClassLoader抽象类,后3个CL均继承自它。作用:加载二进制字节码到方法区,在堆中生成Class反射对象(类声明周期中加载的步骤)。类加载器之间的关系不是继承,而是组合: 成员变量 private final ClassLoader parent;
6.ClassLoader抽象类中的抽象方法:
1.loadClass(String s); 加载指定名字的类。此方法先从已加载的类中找,如果找到了直接返回,没找到则双亲委派;最后调用findClass方法找。
2.findClass:此方法直接返回ClassNotFoundException,自定义CL时重写此方法。
3.findLoadedClass:从当前CL中(metaspace区域)找该类是否已被加载。
4.resolveClass:完成Class对象的链接。
7.双亲委派机制:先给最上面的CL加载,如果不行再依此向下,自己也加载不了则报错。委托是从下向上,具体查找过程是自上至下。 作用: 保证java核心库的安全(自己写的java.lang.String不可能被加载)。
8.同一个ClassLoader加载的同一个类,JVM才认为是相同的类;不同CL加载同一个类,类不一样。
9.反射时,调用此类默认的那个类加载器,保证各种途径加载的是同一个类。
三、手写类加载demo
// 双亲委派 大致源码
Class<?> loadClass(String name, boolean resolve) {
Class<?> c = findLoadedClass(name); //先从已经加载过的类中去找
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false); //用上游的CL加载
} else { //扩展类加载器的parent是null,所以有else
c = findBootstrapClassOrNull(name); //用引导类加载器试试,native方法
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
c = findClass(name); //上游都不行,自己来
}
}
return c;
}
四、Class.forName("java.lang.String") 和 String.class.getClassLoader().loadClass("java.lang.String") 区别是什么?
1.Class.forName("java.lang.String"):反射调用,是主动引用,会进行类的: 加载、链接(解释类)、初始化(执行static)
数据库驱动加载就是使用Class.froName(“com.mysql.jdbc.Driver”):在这个类里面有一个static代码块,里面写的初始化操作
2.ClassLoader.loadClass("java.lang.String"):只做类的加载操作(将字节码文件加载到JVM:方法区中添加类的字节码数据、堆中生成一个反射对象)