类加载顺序
上图所示的是类加载的顺序,按照大的顺序可以分为加载、链接、初始化
其中链接又可以分成验证、准备、解析三个步骤
加载
1.将类的class文件读入到内存中
加载类文件的方式有: 1. 本机文件加载 2.jar包加载 3.网络加载 4.源文件动态编译加载
2.创建一个java.lang.class文件
连接
1.验证:类结构是否正确,是否与其他类协调一致
2.准备:为类分配内存,并设置默认初始值
3.解析:将二进制文件的符号引用替换成直接引用
初始化
对类的变量进行初始化
使用静态代码块初始化、申明类变量初始值
- 子类和父类的关系
父类的静态变量
父类的静态代码块
子类的静态变量
子类的静态代码块
父类非静态变量
父类非静态代码块
父类构造方法
子类非静态变量
子类非静态代码块
子类构造方法
双亲委派模式
java 里有3中默认的类加载器
加载器种类 | 解释 |
---|---|
启动类加载器 | 从JAVA_HOME/lib 路径下找 |
扩展类架子器 | 从JAVA_HOME/lib/ext 路径下找 |
应用程序类加载器 | 从classpath 路径下找 |
自定义加载器 | 我们程序员自己定义的加载器 |
new 一个类的时候 ,首先jvm会去看下当前类加载器有没有父类的加载器,有就把这个加载请求交给父类,一直如此,直到到达最顶层的启动类加载器,然后,在一级一级的往下看能不能加载,能加载就加载,不能就把这个加载任务交给子类
举一个例子,当我new Dog() 的时候
- 首先这个请求会交给应用程序类加载器,然后会被交给扩展类加载器
- 扩展类加载器会将这个请求交给启动类加载器
- 启动类加载器开始尝试去
JAVA_HOME/lib
里找,没有,交还给扩展类加载器 - 扩展类加载器从
JAVA_HOME/lib/ext
里找,没有交还给应用程序类加载器 - 应用程序类从classpath 里去找,没有交给自定义加载器
- 要是自定义加载器还没有就抛出异常
上述的过程只要有一个加载器加载到了就不再下放了。
- 双亲委派机制的优势
可以避免java的核心api 被篡改,比如你自定义了一个String 类,这个类是无效的,他绝对不会被加载,这样有效避免了类加载导致的安全问题
自定义类加载器
接下去我们实现一个自定义的类加载器,利用这个加载器来加载一个类
-
首先简单的构建一个类
package com.example.demo; public class Dog { public String name; }
这是一个很简单的类,我们把它编译成class 文件放在
d:
盘的根目录下 -
构建我们自己的类加载器
public class MyLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { File file = new File("D:/Dog.class"); try { FileInputStream fileInputStream = new FileInputStream(file); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); //每次读取的大小是4kb byte[] b = new byte[4 * 1024]; int n = 0; while ((n = fileInputStream.read(b)) != -1) { outputStream.write(b, 0, n); } //将Dog.class类读取到byte数组里 byte[] classByteArray = outputStream.toByteArray(); //调用defineClass 将byte 加载成class Class<?> aClass = this.defineClass(name, classByteArray, 0, classByteArray.length); return aClass; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } }
上面的代码中,我们自定义了一个类让他去继承
ClassLoader
,然后重写其中的findClass
方法,我们重写后的逻辑就是自己的指定的目录,也就是d盘
下找到Dog.class
文件 然后读取到一个byte数组中,然后调用defineClass
加载这个类。 -
下面用一个简单的程序测试下
public class UserDemo { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyLoader myLoader = new MyLoader(); Class<?> aClass = Class.forName("com.example.demo.Dog", true, myLoader); Object o = aClass.newInstance(); System.out.println(o); System.out.println(o.getClass().getClassLoader()); } }
这里的Class.forname 和以前不一样,我们后面对了两个参数,true代表类重新加载,myLoader就是我们自定义的类加载器
运行后显示:
com.example.demo.Dog@2e817b38 com.example.demo.MyLoader@4d405ef7
可以看到已经使用了我们自定义的类加载器。
-
这里有一点要注意,就是个别编译器比较智能,如我使用的idea,他们会自定编译,直接把你的java文件编译成class 放入classpath下去了,所以在运行请务必吧这个文件删除,如果你用的是idea 把整个target删除就行