相信每一位java开发工程师或多或少的对类加载有所了解,程序中通过java编译器将一个.java文件编译为.class文件,.class文件中包含着java代码转换后的虚拟机指令,当需要某个类时,虚拟机加载它的.class文件,并创建相对应的对象,将.class文件加载到jvm虚拟机内存,这个过程就称之为类加载过程;
在上述过程中,一共可划分为两个步骤,别分是:编译、运行;
编译:把我们编写好的java文件通过javac命令编译成字节码,也就是我们通常说的.class文件。
运行:将编译好的.class文件交给java虚拟机(jvm)执行生成对应的class对象装载到内存的过程;
如果用一张图来描述类的加载过程
1、 加载:在此阶段,编译.java文件,生成二进制字节流.class文件,将其静态存储结构转换成方法区的运行时数据结构,在堆内存中生成一个java.lang.Class对象,作为访问方法区中数据的入口;
2、 验证:验证过程主要通过四个阶段来确保加载的类的正确性;分别是文件格式的验证(包含版本信息等)、字节码验证、元数据验证、符号引用验证;
3、 准备:为类的静态变量分类内存,并初始化默认值这一过程;
4、 解析:比如:将类中的符号引用转换成直接引用;
5、 初始化:为类的静态变量赋予正确的初始值,jvm负责对类进行初始化,主要对类变量进行初始化。
Jvm在启动时,并不会一次性加载所有的class文件,而是根据需要去动态加载;
类的加载器
在java系统中的类加载器有三种;
1、 AppClassLoader :主要是加载当前应用classPath目录下所有类;
2、 ExtClassLoader :扩展的类加载器,加载目录%JRE_HOME%libext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录;
3、 BoostrapClassLoader:主要是加载核心类库,如加载java.lang.System,主要加载%JRE_HOME%lib下的rt.jar、resources.jar、charsets.jar和class等
Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader。
AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托交给父类加载器,父类加载器又将加载任务向上委托,直到最父类加载器,如果最父类加载器可以完成类加载任务,就成功返回,如果不行就向下传递委托任务,由其子类加载器进行加载。
这个就是双亲委派机制的具体体现形式,其优点是保证了java核心库的安全性(例如:如果用户自己写了一个java.lang.Long类就会因为双亲委派机制不能被加载,不会破坏原生的Long类的加载);
那么,如果我们自定义一个java.lang.Long类,由于类加载器的双亲委派机制,这个自定义的类是不会被加载器加载的,因为加载器会加载系统核心类库中的System类;
在上面我们编写了一个简单的类,不过这个类的包路径是java.lang;然后当运行程序中的main方法时,直接抛出异常禁止使用包名:java.lang;
根据异常信息我们可以看到
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
在程序ClassLoader.java:662行被抛出异常,跟进去看一下:
根据源码中的代码示意
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
如果全限制名为null或者是全限制名是以java.开始,则抛出异常;也就是说,我们自己编写代码是不能定义包路径为java开头的;也就是说当如果类的全路径名以java.开头时,就会报错;
自定义类加载器
通过上面的学习,我们知道了类的三种默认加载器以及分别应用场景以及优先级关系;我们看一下自定义类加载器;
我们自定义类加载器必须继承ClassLoader类重写findClass方法,我们可以看一下ClassLoader类中loadClass方法都做了哪些处理;
在上面程序的424行,可以看到c = findClass(name); 可以看到在这个方法中并没有做任何逻辑处理,而是直接抛出一个异常,并且,这个方式是被protected修饰的;
所以,我们自定义类加载器,直接重写这个findClass方法即可;
在这里,自定义类加载器,如果我们想继续遵循双亲委派模型,我们直接重写findClass方法即可;
如果需要打破双亲委派模型,我们就需要重写整个loadClass方法,细心一点会发现,loadClass方法也是被protected修饰的;
所以在自定义类加载器时,可以根据实际应用场景来确定是否需要遵循系统中原有的双亲委派模型;
我们顺便再看一下源码的650行preDefineClass方法;
私有的preDefineClass方法中限定了程序包名的命名规则;那么,当破坏了双亲委派模型后是否还能加载自定义的java.开头的包名类呢?
2019年7月9日 17:15:56