类加载是一个将 .class 字节码文件实例化成 Class 对象并进行相关初始化过程。
类加载过程中, JVM 会初始化继承树还没有被初始化过所有父类,并且会执行这个链路上所有未执行过的静态代码块,静态变量赋值语句等。某些类在使用时,也可以按需由类加载器进行加载。
Java 的类加载器是一个运行时核心基础设施模块,主要在启动进行类的 Load , Link 和 Init ,即加载,链接,初始化。
第一步, Load 阶段读取类文件产生二进制流,并转化特定数据结构,初步校验 cafe babe 魔法数,常量池,文件长度,是否有父类等。然后创建对应的 java.lang.Class 实例。
第二步, Link 阶段包括校验,准备解析三个步骤.校验是更为详细的校验.比如: final 是否合规,类型是否正确,静态变量是否合理等。
准备阶段是为静态变量分配内存,并设置默认值,解析类和方法确保类与类之间相互引用的正确性,完成内存的结构布局。
第三步, Init 阶段执行类构造器 方法,如果赋值运算是通过其他类的静态方法完成的,那么会马上解析另一个类,在虚拟机栈中执行完毕后返回值进行赋值
ClassLoader 的作用是啥? ClassLoader 的使用是提前加载 .class 类文件存在内存中。
ClassLoader 加载类涉及的模型?
双亲委托派模型。低层次的当前类加载器,不能覆盖高层次类加载器已经加载的类。如果低层次的类加载器想加载一个未知的类,要非常礼貌的向上逐步询问:请问,这个类已经加载过了吗?"被询问的高层次类加载器会问两个问题:第一次我是否一家在过这个类,如果没有,是否可以加载此类?如果党所偶高层次类加载器的两个问题上大的答案均为否时候,才可以让当前类加载器加载该未知类.知道顶层类加载器 BootStrapClassLoader
class 和 大写 Class 有啥区别? 全小写的 class 是关键字,用来定义类,而首字母大写的 Class ,它是所有 class 的类。这句话理解起来有难度
public class ClassTest {
// 数组类型有一个魔法属性: length 来获取数组长度
private static int[] array = new int[3];
private static int length = array.length;
// 任何小写 `class` 定义的类,也有一个魔法属性: class,来获取此类的大写 `Class` 类对象
private static Class<One> one = One.class;
private static Class<Another> another = Another.class;
public static void main(String[] args) {
// 通过newInstance 方法创建 One 和 Another 的类引用(第一处)
try {
final One oneObj = one.getDeclaredConstructor().newInstance();
oneObj.call();
// 通过 one 这个大写的Class对象,获取私有成员属性对象Filed(第二处)
final Another anotherObj = another.newInstance();
anotherObj.speak();
final Field inner = one.getDeclaredField("inner");
inner.setAccessible(true);
inner.set(oneObj, "world changed.");
// 成功修改类的私有属性 inner 变量值 为world changed
System.out.println(oneObj.getInner());
} catch (Exception e) {
e.printStackTrace();
}
}
class One {
private String inner = "time files";
public void call() {
System.out.println("hello world.");
}
public String getInner() {
return inner;
}
}
class Another {
public void speak() {
System.out.println("easy coding");
}
}
}
复制代码
new 和 newInstance 的区别?
new 是强类型校验,可以调用任何构造方法,在使用 new 的时候,这个类可以没被加载过,而 Class 类的 newInstance 是弱类型,只能调用无参数构造方法,如果没有默认构造方法,就抛 InstantiationException 异常;
如果此构造方法没有权限访问,则抛出 IllegalAcessExecption 异常。 Java 通过类加载器把类的实现与类的定义进行解耦,所以是实现面向接口编程,依赖倒置的必然选择。
private 成员在类外是否可以修改?
通过 inner.setAccessible(true) 操作,即可使用 Class 类的 set 方法修改其值.如果没有就会抛出 IllegalAcessExecption 。
类加载器有着严格的等级制度,最高一层是家族威望最高的 Bootstrap ,它是在 jvm 启动时创建的,通常由于操作系统相关本地代码实现,负责装载最核心的 Java 类,比如:
Object , System , String 等 第二层是在 JDK9 版本中,称为 Platform ClassLoader 即为平台类加载器,用以加载一些扩展的系统类,如:
XML,加密,压缩相关类
JDK9 之前的加载器是 Extention ClassLoader ;
第三层是 Application ClassLoader 的应用类加载器,主要市价在用户定义的 CLASSPATH 路径的类。
第二,第三层类加载器为 Java 语言实现,用户也可以自定义类加载器。
在同一个工程内引用多个框架时,往往被迫进行类仲裁.按照某种规则 jar 包的版本被统一指定,导致应用程序出现异常,主流的类框架都会自定义类加载器,实现不同中间件的类隔离,避免了类冲突。
Java 代码容易被编译或篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密字节码。
自定义类加载器的步骤有如下:
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
final byte[] bytes = getClassFromCustomPath(name);
if (bytes == null){
throw new FileNotFoundException();
}else {
return defineClass(name,bytes,0,bytes.length);
}
throw new ClassNotFoundException();
}
private byte [] getClassFromCustomPath(String name){
// 自定义路径中加载类
}
public static void main(String [] args){
final CustomClassLoader customClassLoader = new CustomClassLoader();
try {
final Class<?> one = Class.forName("One", true, customClassLoader);
final Object o = one.newInstance();
System.out.println(o.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
复制代码
增加 -XX: + TracerClasLoading 参数,此参数在解决类冲突时非常实用,毕竟不同 JVM 环境对于加载类的顺序并非一致的。
有时想观察特定类加载上下文,由于加载数量众多,调试的时候很难捕捉类的加载过程,这时候可以使用条件断点功能
在本地类加载的方式一般我们是这么使用的:
// 正在使用的类加载器:AppClassLoader ClassLoader c = MicroKibaco.class.getClassLoader(); // AppClassLoader 的父加载器 PlatformClassLoader ClassLoader c1 = c.getParent(); // PlatformClassLoader 的父加载器是BootStrap.它是c++实现的,返回null ClassLoader c2 = c1.getParent(); 复制代码
最高一层 BootStrap 是通过 c++ 实现的,并不存在 JVM 体系内,所以输出为null,类加载具有等级制度,但是并非继承关系,以组合方式复用父加载器的功能,这也符合组合有限原则。
但是双亲委派模型并不是强制必须的,对于自己加载不了的类怎么办?直接用线程上下文类加载器完成,通过:
ClassLoader cl = Thread.currentThread().getContextClassLoader(); 复制代码
这条语句获取本地线程然后实现上下类加载。 所以这个地方 Bootstrap Classloader 加载器拿到了 Application , ClassLoader 加载器应该加载的类,就打破了双亲委派模型。