java类加载器ClassLoader
更多资料
1.类加载器是什么?
类加载器就是用来加载类的东西!类加载器也是一个类:ClassLoader
类加载器可以被加载到内存,是通过类加载器完成的!
主要分三类:
- BootStrap:引导类加载器,加载rt.jar中的类
- ExtClassLoader:扩展类加载器,加载lib/ext目录下的类
- AppClassLoader:系统类加载器,加载CLASSPATH下的类,即我们写的类,以及第三方提供的类
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载
2.类加载过程
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
2-1.加载
将class文件读入内存,并为之创建一个Class对象。
2-2.连接
分三个小阶段
- 验证: 是否有正确的内部结构,并和其他类协调一致
- 准备: 为类的静态成员分配内存,并设置默认初始化值
- 解析: 将类的二进制数据中的符号引用替换为直接引用
2-3.初始化
类会在首次被“主动使用”时执行初始化,为类(静态)变量赋予正确的初始值。在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。
两个步骤:
- 如果类存在直接父类的话,且直接父类还没有被初始化,则先初始化其直接父类
- 如果类存在一个初始化方法,就执行此方法
初始化接口并不需要初始化它的父接口
类初始化方式
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
3.类加载器
类加载器 | 名称 | 作用 |
---|---|---|
BootstrapClassLoader | 根类加载器 | 负责Java核心类的加载,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中。 |
ExtensionClassLoader | 扩展类加载器 | 负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录 |
SysetmClassLoader | 系统类加载器 | 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。 |
4.jvm中相同的类
一个classloader中不会出现相同的类,在不同的classloader中可以出现同名的类。
在JVM中,不可能存在一个类被加载两次的事情!一个类如果已经被加载了,当再次试图加载这个类时,类加载器会先去查找这个类是否已经被加载过了,如果已经被加载过了,就不会再去加载了。
但是,如果一个类使用不同的类加载器去加载是可以出现多次加载的情况的!也就是说,在JVM眼中,相同的类需要有相同的class文件,以及相同的类加载器。当一个class文件,被不同的类加载器加载了,JVM会认识这是两个不同的类,这会在JVM中出现两个相同的Class对象!甚至会出现类型转换异常!
5.双亲委托机制
首先委托类加载器的父类去加载,如果父类无法加载则自己加载
当系统类加载器去加载一个类时,它首先会让上级去加载,即让扩展类加载器去加载类,扩展类加载器也会让它的上级引导类加载器去加载类。如果上级没有加载成功,那么再由自己去加载!
例如
我们自己写的Person类,一定是存放到CLASSPATH中,那么一定是由系统类加载器来加载。当系统类加载器来加载类时,它首先把加载的任务交给扩展类加载器,如果扩展类加载器加载成功了,那么系统类加载器就不会再去加载。这就是代理模式了!
同理,扩展类加载器也会把加载类的任务交给它的“上级”,即引导类加载器,引导类加载器加载成功,那么扩展类加载器也就不会再去加载了。引导类加载器是用C语言写的,是JVM的一部分,它是最上层的类加载器了,所以它就没有“上级了”。它只负责去加载“内部人”,即JDK中的类,但我们知道Person类是我们自己写的类,所以它加载失败。
代理模式保证了JDK中的类一定是由引导类加载加载的!这就不会出现多个版本的类,这也是代理模式的好处。
如果对ClassLoader里的加载流程源码感兴趣可以自己看下源代码,或者参考下面的一篇博客
6.如何自定义类加载器
自定义的加载器需要继承ClassLoader类来完成自定义类加载器。 ClassLoader加载类都是通过loadClass()方法来完成的 。
ClassLoader类中的方法
方法 | 说明 |
---|---|
getParent() | 获取上级类加载器 |
loadClass() | 实现了类加载的加载流程,也就是算法框架 |
findLoadedClass() | 查看该类是否被加载过 |
findClass() | 真正去加载类,自定义类加载器需要重写的方法 |
defineClass() | 把Class的字节数组byte[]转成Class |
loadClass方法主要以下几个步骤:
- findLoadedClass 方法,查看类是否已经被加过了
- 没有加过的话,调用父类加载器,由父类加载器加载class,如getParent().loadClass()返回的不是null,这说明上级加载成功了,那么就加载结果
- 如果父类加载也没加载到,那么再调用自己的 findClass 方法来加载类
所以,我们只需要重写 findClass 方法就可以了,而不用重写loadClass方法,会覆盖了原来的代理模式。
findClass 方法中又主要做了一下几件事:
- 加载class文件, 把它加载到一个byte[]中
- 调用defineClass方法,把byte[]传递给这个方法
源码
java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> 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 stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
通过ClassLoader里的loadClass方法,可以看出三个步骤
自定义ClassLoader
public class FileSystemClassLoader extends ClassLoader {
private String classPath;
public FileSystemClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] datas = getClassByte(name);
if (datas == null) {
throw new ClassNotFoundException("类没有找到:" + name);
}
return this.defineClass(name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类找不到:" + name);
}
}
private byte[] getClassByte(String packageFullName) throws IOException {
// 将包里的.替换成\
String namePath = packageFullName.replaceAll(".", "\\") + ".class";
File file = new File(classPath, namePath);
// 使用commons-io的FileUtils工具类将文件读取到内存byte[]
return FileUtils.readFileToByteArray(file);
}
}
实体类
public class Student {
private String name;
private String info;
public String doing(String something) {
return "doing..." + something;
}
}
将Student.java类编译成class文件,放到一个目录下(跟下面测试类的路径一致)
测试类
public class FlieSystemClassLoaderTest {
public static void main(String[] args) {
ClassLoader loader = new FileSystemClassLoader("D:\\yuxlhome\\del");
try {
Class<?> clazz = loader.loadClass("com.iwork.java.base09.reflect.chapter01.Student");
Object obj = clazz.newInstance();
Method doingMethod = clazz.getMethod("doing", String.class);
String result = (String) doingMethod.invoke(obj, "study");
System.out.println(result);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
7.更多学习资料
需要注册个人用户才能看到全部菜单
网站覆盖 “前端/后端/运维/测试” 常见技术的资料,为您提供丰富且全面的IT学习手册,不用再为找IT学习资料而烦恼。内容从入门到高级再深入源码,由浅入深,由点到面,为您提供一套完整IT学习路线图及学习手册,让您从小白到精通,由菜鸟到大神。只要您有一颗好学之心,就能助您早日成"神"。
我们也期待任何有意朋友能加入我们,一起打造一套完整且全面的IT学习手册,能给需要的人员提供一点思路或者帮助,为每一位"IT修神"之路的人,推一把力,助一次攻,留下一点足迹…
上一篇: ClassLoader类加载器
下一篇: 系统日志管理