本文共 4486 字,大约阅读时间需要 14 分钟。
本次主要认知类加载的机制、详细介绍类加载器、类加载的过程以及类加载过程中的双亲委托原则。
在介绍类的加载机制之前,我们先看一下类加载机制在Java程序运行期间处于一个什么样的环节。从上图可以看出,Java文件通过Java编译器,编译成了字节码文件,接下来类加载器将这些字节码文件加载到虚拟机内存。但是哪些字节码文件在哪些时机需要加载到内存中,也就是类的加载时机是啥?
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它。如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
JVM中类加载器主要有BootStrapClassLoader(引导类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)、CustomClassLoader(用户自定义类加载器)。
双亲委托原则是Java类加载机制的重要组成部分。它决定了当一个类加载器无法找到所需类时,该如何递归查找。具体流程如下:
首先,自定义的加载器,先查找本地是否已经加载,如果没有加载,再利用递归,委托父类加载器检查是否已经加载,层层判断一直到Bootstrap ClassLoader;
其次,如果通过层层查找,发现都没有加载,再从顶层开始,判断各个加载器扫描的目录里面查找目标类:
package com.jason.util.classloader;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;public class MyClassLoader extends ClassLoader { private String mLibPath; public MyClassLoader(String path) { mLibPath = path; } protected Class findClass(String name) throws ClassNotFoundException { String fileName = getFileName(name); File file = new File(mLibPath, fileName); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; while ((len = is.read()) != -1) { bos.write(len); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name, data, 0, data.length); } catch (IOException e) { e.printStackTrace(); } return super.findClass(name); } private String getFileName(String name) { int index = name.lastIndexOf('.'); if (index == -1) { return name + ".class"; } else { return name.substring(index) + ".class"; } }}
package com.jason.util.classloader;import java.lang.reflect.Method;public class TestClassLoader { public static void main(String[] args) { MyClassLoader diskLoader = new MyClassLoader("D:\\"); try { Class c = diskLoader.loadClass("TestHello"); if (c != null) { try { Object obj = c.newInstance(); Method method = c.getDeclaredMethod("hello", null); method.invoke(obj, null); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } }}
类加载过程分为五个阶段:加载、验证、准备、解析、初始化。
加载阶段是类加载机制的第一个过程。在加载阶段,虚拟机主要完成三件事:
相对于类加载的其他阶段而言,加载阶段是可控性最强的阶段,因为程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载。我们在最后一部分会详细介绍这个类加载器。在这里我们只需要知道类加载器的作用就是上面虚拟机需要完成的三件事,仅此而已。
验证的主要作用就是确保被加载的类的正确性。也是连接阶段的第一步。说白了也就是我们加载好的.class文件不能对我们的虚拟机有危害,所以先检测验证一下。他主要是完成四个阶段的验证:
对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,如果我们的代码能够确保没有问题,那么我们就没有必要去验证,毕竟验证需要花费一定的的时间。当然我们可以使用-Xverfity:none来关闭大部分的验证。
准备阶段主要为类变量分配内存并设置初始值。这些内存都在方法区分配。在这个阶段我们只需要注意两点就好了,也就是类变量和初始值两个关键词:
解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程。符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)
直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
这是类加载机制的最后一步。在这个阶段,Java程序代码才开始真正执行。我们知道,在准备阶段已经为类变量赋过一次值。在初始化阶段,主要为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
JVM初始化步骤:
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
转载地址:http://visn.baihongyu.com/