类加载过程简介
类的加载过程主要包括: 加载阶段、连接阶段、初始化阶段
加载阶段: 加载类的二进制数据文件(class 文件)
连接阶段:
验证: 确保类文件的正确性
准备: 类的静态文件分配内存,并且初始化默认值
解析: 类中的符号引用转换为直接引用
初始化阶段: 为类的静态变量赋予正确的初始值
在Java 程序启动之后,可能会包含很多的类,当然并不是每一个类都不会被初始化,JVM 对类的初始化是一个延迟的机制,也就是lazy 的。
类的主动使用和被动使用
主动使用: 会使得类加载并初始化
- new 关键字创建一个类
- 访问类的静态变量包括读取和更新都会使得类初始化
- 访问类的静态方法包括读取和更新都会使得类初始化
- 对类进行反射操作的时候
- 初始化子类导致父类的初始化
- 当一个类中包括main 函数的时候,当这个main 函数得到启动的时候,这个类会初始化
被动使用: 不会使得类加载并初始化
- 问类的静态常量(final 修饰的变量)不会使得类初始化
- 构建数组的时候并不会使得类初始化
Simple[] simples = new Simple[10];
类的初始化阶段
类的初始化过程是整个类加载过程中最后一个阶段,在这个阶段做的做主要的一件事情就是执行() 方法,在这个方法中所有的类变量都会被赋予正确的值,也就是编写的时候指定的值。这个方法中包含所有类变量的赋值动作和静态语句块的执行代码,
另外() 方法和类的构造函数有所不同,它不需要显示的调用父类的构造器,虚拟机会保证父类的 () 方法最先执行,因此父类的静态变量总是能够得到优先赋值。
当主动使用触发了类的初始化之后就会调用这个方法。
static {try {System.out.println("static code will be inviked");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {IntStream.range(0,5).forEach(i -> new Thread(DeadLock::new));}
执行上面的代码片段,在同一个世界,只能有一个线程执行到静态代码块中的内容,并且静态代码块中仅仅会被执行一次,JVM 保证了 () 方法在多线程的执行环境下的同步。
类的加载器
类的加载器负责类的加载职责,任何一个对象的class 在JVM 中都只存在唯一的一份。
JVM 有三大类加载器,不同的加载器负责将不同的类加载到JVM 内存之中,并且它们之中严格遵守这父委托机制。
Bootstrap
根加载器,为最顶层的加载器,主要负责虚拟机核心类库的加载如Java.lang库的加载,可以通过-Xbootclasspath 来指定根加载器的路径,也可以通过类属性来得知当前Jvm 的根加载器都加载类哪些资源。
System.out.println("Bootstrap" + String.class.getClassLoader());null
System.out.println(System.getProperty("sun.boot.class.path"));/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar
第一个获取的classloader 会是null,是因为根类加载器是获取不到引用的。
第二个输出更加在的加载路径
ExtClassLoader
扩展类加载器,这个类加载器是根加载器,它主要是加载JAVA_HOME 下的jre/lb/ext 目录里面的类库
System.out.println(System.getProperty("java.ext.dirs"));
获取扩展类加载资源的路径
ApplicationClassLoader
系统类加载器是一种常见的类加载器,其负责加载 classpath 下的类库资源,
System.out.println(System.getProperty("java.class.path"));
System.out.println(DeadLock.class.getClassLoader());
第一个打印出当前系统类加载器资源的路径,
第二个打印出当前类的类加载器,会输出
sun.misc.Launcher$AppClassLoader@18b4aac2
自定义类加载器
当然也是可以进行自定义的类加载器的
package thread.chapt1;public class MyClassLoader extends ClassLoader {private final static Path DEFAULT_CLASS_DIR = Paths.get("/Users/xuxliu/Ifoods/Java");// 默认的class 路径public MyClassLoader() {super();this.classDir = DEFAULT_CLASS_DIR;}private final Path classDir;// 指定父类加载器// 允许传入指定路径的 class 路径public MyClassLoader(String classDir, ClassLoader parent) {super(parent);this.classDir = Paths.get(classDir);}// 指定加载特定的目录public MyClassLoader(String classDir) {super();this.classDir = Paths.get(classDir);}// 重写父类的 findClass 这是非常重要的@Overrideprotected Class> findClass(String name) throws ClassNotFoundException {// 读取二进制数据byte[] classBytes = new byte[0];try {classBytes = this.readClassBytes(name);} catch (IOException e) {e.printStackTrace();}if (classBytes == null || classBytes.length == 0) {throw new ClassNotFoundException("can not load the class");}// 调用 defineClass 方法定义class,读取class 文件return this.defineClass(name, classBytes, 0, classBytes.length);}// class 文件读入内存private byte[] readClassBytes(String name) throws ClassNotFoundException, IOException {// 包名分隔符专为文件路径String classPath = name.replace(".", "/");Path classFullPath = classDir.resolve(Paths.get(classPath) + name);if (!classFullPath.toFile().exists()){throw new ClassNotFoundException("the class " + name + " not found");}try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {Files.copy(classFullPath, baos);return baos.toByteArray();}}@Overridepublic String toString() {return "MyClassLoader";}public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {MyClassLoader myClassLoader = new MyClassLoader();Class> aClass = myClassLoader.loadClass("thread.chapt1.HelloWorld");System.out.println(aClass.getClassLoader());Object helloWorld = aClass.newInstance();System.out.println(helloWorld);Method welcome = aClass.getMethod("welcome");String result = (String) welcome.invoke(helloWorld);System.out.println(result);}
}上面的代码先对程序进行编译,然后将编译后的class 文件夹复制到 /Users/xuxliu/Ifoods/Java 路径下
这里还需要删除掉 .java 文件,因为父委托机制会破坏我们的测试代码
程序在执行过程中,主要步骤是
1. myClassLoader.loadClass("thread.chapt1.HelloWorld");
2. 执行 readClassBytes 读取 class 文件
3. findClass 找到class
4. 在 findClass 方法中最后执行 defineClass 就定义好了,HelloWorld 类
5. Object helloWorld = aClass.newInstance(); 创建 HelloWorld 类对象
6. Method welcome = aClass.getMethod("welcome"); 反射获取方法
7. String result = (String) welcome.invoke(helloWorld); 反射执行方法
双亲委托机制
类加载中最重要的机制,双亲委托机制又称为是父委托机制,一个类加载器调用LoadClass 之后,它并不会直接将其加载,而是先交给当前父加载器尝试加载直到最顶层的父加载器。然后再依次向下加载。
上面的例子中,为了防止双亲委托机制干扰到我们的测试,所以在执行的时候,将 .java 文件删除了。当然也是有不用删除就能让双亲委托机制不干扰测试代码的,也就是Load class 的时候,使用自定义的类。
protected Class> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();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.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
以上代码是LoadClass 中的源码,从源码中加载类的方式是
1. 当前类加载器中根据类的全路径名查询是否存在该类,如果存在则直接返回
2. 如果不存在,查看当前类是否存在父类加载器,存在,则直接调用父类加载器加载类
3. 如果当前父类不存在加载器,那么直接调用根类加载器对类进行加载
4. 如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass 方法加载,这个方法就是我们定义加载器需要重写的方法
所以有两种方式可以绕过系统类加载器,直接讲扩展类加载器作为MyClassLoader 的父加载器,也就是上面我们另外的一个构造函数
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {ClassLoader classLoader = MyClassLoader.class.getClassLoader();// 这个是原来的构造函数
// MyClassLoader myClassLoader = new MyClassLoader();// 这个是新的指定加载器的构造函数MyClassLoader myClassLoader = new MyClassLoader("/Users/xuxliu/Ifoods/Java/", classLoader);// 这也也可以破坏,双委托机制
// MyClassLoader myClassLoader = new MyClassLoader("/Users/xuxliu/Ifoods/Java/", null);Class> aClass = myClassLoader.loadClass("thread.chapt1.HelloWorld");System.out.println(aClass.getClassLoader());Object helloWorld = aClass.newInstance();System.out.println(helloWorld);Method welcome = aClass.getMethod("welcome");String result = (String) welcome.invoke(helloWorld);System.out.println(result);}
上面的加载机制都还是没有避免一层一层的委托,也可以绕过这种机制,那么就是重写loadClass 方法。