本篇文章来简析一下 classloader 在 Java 中的应用。java
如今通常一个应用程序开发会包含不少不少的类, Java 程序启动时并非一次性将全部的类所有加载到内存中进行运行的,而是先加载部分的类到 JVM 中,而后等 JVM 须要用到其余的类时再加载进去,这样的好处就是节约内存,提升了效率。c++
在 Java 中类加载器就是 ClassLoader , ClassLoader 的具体做用就是将 class 文件加载到 jvm 虚拟机中去,程序就能够正确运行了。算法
咱们日常写的 Java 文件的格式是 xxx.java 文件格式的,这个格式并非 JVM 执行的格式, JVM 执行的是 .class 格式的文件,这就须要将 .java 的格式文件转为 .class 的格式,这就是编译过程。命令是:安全
javac HelloWorld.java
复制代码
经过 javac 命令便可在当前目录下面生成 .class 文件,这个文件就是 JVM 可以执行的文件格式。bash
接着经过 java 命令便可运行这个文件:jvm
java HelloWorld
复制代码
打印结果以下所示: ide
每一个 ClassLoader 对象都是一个 java.lang.ClassLoader 的实例。每一个Class对象都被这些 ClassLoader 对象所加载,经过继承java.lang.ClassLoader 能够扩展出自定义 ClassLoader,并使用这些自定义的 ClassLoader 对类进行加载。学习
package java.lang;
public abstract class ClassLoader {
public Class loadClass(String name);
protected Class defineClass(byte[] b);
public URL getResource(String name);
public Enumeration getResources(String name);
public ClassLoader getParent();
Class<?> findClass(String name)
//...
}
复制代码
它接受一个全类名,而后返回一个 Class 类型的实例。测试
方法接受一组字节,而后将其具体化为一个 Class 类型实例,它通常从磁盘上加载一个文件,而后将文件的字节传递给 JVM ,经过 JVM ( native 方法)对于 Class 的定义,将其具体化,实例化为一个Class 类型实例。ui
返回其parent ClassLoader
咱们经过实际demo来测试一下。
package demo;
public class Test {
public static void main(String[] args) {
ClassLoader classLoader = Test.class.getClassLoader();
System.out.println(classLoader);
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
}
}
复制代码
打印结果以下:
sun.misc.Launcher$AppClassLoader@135fbaa4
sun.misc.Launcher$ExtClassLoader@2503dbd3
null
复制代码
在 Java 中提供了如下三种类加载 ClassLoader:
其实上面的 demo 打印出来的结果就验证了这三种 ClassLoader。
(1): 根类加载器(null)
它是由本地代码(c/c++)实现的,你根本拿不到他的引用,可是他实际存在,而且加载一些重要的类,它加载(%JAVA_HOME%\jre\lib),如rt.jar(runtime)、i18n.jar等,这些是Java的核心类。
(2): 扩展类加载器(ExtClassLoader)
虽然说能拿到,可是咱们在实践中不多用到它,它主要加载扩展目录下的jar包, %JAVA_HOME%\lib\ext
(3): 应用类加载器(AppClassLoader)
它主要加载咱们应用程序中的类,如Test,或者用到的第三方包,如jdbc驱动包等。
这里的父类加载器与类中继承概念要区分,它们在class定义上是没有父子关系的。
一样的,在 Java 中的 ClassLoader 存在一种调用的顺序,咱们就以上面的 Test.java 类进行解析。
当 Test.class 要进行加载时,它将会启动应用类加载器进行加载Test类,可是这个应用类加载器不会真正去加载他,而是会调用看是否有父加载器,结果有,是扩展类加载器,扩展类加载器也不会直接去加载,它看本身是否有父加载器没,结果它仍是有的,是根类加载器。
因此这个时候根类加载器就去加载这个类,可在%JAVA_HOME%\jre\lib 下,它找不到 demo.Test 这个类,因此他告诉他的子类加载器,我找不到,你去加载吧,子类扩展类加载器去 %JAVA_HOME%\lib\ext 去找,也找不着,它告诉它的子类加载器 AppClassLoader,我找不到这个类,你去加载吧,结果AppClassLoader 找到了,就加到内存中,并生成 Class 对象。
借用网上的一张图来表示流程。
这张图很明显的展现了 ClassLoader 的执行顺序流程。
双亲委托即“类装载器有载入类的需求时,会先请示其 Parent 使用其搜索路径帮忙载入,若是 Parent 找不到,那么才由本身依照本身的搜索路径搜索类”,上面那张图就显示了该流程。
咱们仍是使用上面的代码进行说明:
package demo;
public class Test {
public static void main(String[] args) {
ClassLoader classLoader = Test.class.getClassLoader();
System.out.println(classLoader);
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
}
}
复制代码
打印结果以下:
sun.misc.Launcher$AppClassLoader@135fbaa4
sun.misc.Launcher$ExtClassLoader@2503dbd3
null
复制代码
打印结果能够看出,Test是由 AppClassLoader 加载器加载的,AppClassLoader 的 Parent 加载器是 ExtClassLoader ,可是ExtClassLoader 的 Parent 为 null 是怎么回事呵,朋友们留意的话,前面有提到 Bootstrap Loader 是用 C++ 语言写的,依 java 的观点来看,逻辑上并不存在 Bootstrap Loader 的类实体,因此在 java 程序代码里试图打印出其内容时,咱们就会看到输出为 null。
为何要有“委托机制”?能够从安全方面考虑,若是一我的写了一个恶意的基础类(如java.lang.String)并加载到 JVM 将会引发严重的后果,但有了全盘负责制,java.lang.String 永远是由根装载器来装载,避免以上状况发生。
假如咱们本身写了一个 java.lang.String 的类,咱们是否能够替换调JDK 自己的类?
答案是否认的。咱们不能实现。为何呢?我看不少网上解释是说双亲委托机制解决这个问题,其实不是很是的准确。由于双亲委托机制是能够打破的,你彻底能够本身写一个 classLoader 来加载本身写的java.lang.String 类,可是你会发现也不会加载成功,具体就是由于针对java.* 开头的类,jvm 的实现中已经保证了必须由 bootstrp 来加载。
既然系统已经存在三种 ClassLoader 为何还须要咱们本身定义 ClassLoader 呢?由于 Java 中提供的默认 ClassLoader 只加载指定目录下面的 jar 和 class ,可是若是咱们须要加载其余地方的 jar 和 class 时则无能为力了,这个时候就须要咱们本身实现 ClassLoader 了。咱们从上面了解到 ClassLoader是一个抽象类,实现自定义的 ClassLoader 须要继承该类并实现里面的方法。通常状况下,咱们重写父类的 findClass 方法便可。
package java.lang;
public abstract class ClassLoader {
public Class loadClass(String name);
protected Class defineClass(byte[] b);
public URL getResource(String name);
public Enumeration getResources(String name);
public ClassLoader getParent();
Class<?> findClass(String name)
//...
}
复制代码
ClassLoader 方法那么多为何只重写 findClass 方法? 由于 JDK 已经在 loadClass 方法中帮咱们实现了 ClassLoader 搜索类的算法,当在 loadClass 方法中搜索不到类时,loadClass 方法就会调用findClass 方法来搜索类,因此咱们只需重写该方法便可。如没有特殊的要求,通常不建议重写 loadClass 搜索类的算法。
假如咱们自定义一个 classloader 咱们能够编写一个测试类来讲明。在当前目录下面新建一个 Hello 类。里面有个方法 sayHello 而后放入到指定目录下面,如:我当前的目录为:
/Users/java/intellidea/JavaTest/src/demo/Hello.java
package demo;
public class Hello {
public void sayHello() {
System.out.println("say hello ------> classloader");
}
}
复制代码
接着咱们须要自定义一个 ClassLoader 来继承系统的 ClassLoader。咱们命名为 HelloClassLoader 类。
package demo;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class HelloClassLoader extends ClassLoader {
private String mLibPath;
public HelloClassLoader(String path) {
mLibPath = path;
}
@Override
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;
try {
while ((len = is.read()) != -1) {
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name,data,0,data.length);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
//获取要加载 的class文件名
private String getFileName(String name) {
// TODO Auto-generated method stub
int index = name.lastIndexOf('.');
if(index == -1){
return name+".class";
}else{
return name.substring(index)+".class";
}
}
}
复制代码
在HelloClassLoader 类中咱们经过 findClass() 方法来查找咱们用到的 Hello.class 文件,从而生成了 Class 对象。接着咱们编写HelloClassLoaderTest 测试类进行测:
package demo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HelloClassLoaderTest {
public static void main(String[] args) throws InvocationTargetException {
// TODO Auto-generated method stub
//建立自定义classloader对象。
HelloClassLoader diskLoader = new HelloClassLoader("/Users/java/intellidea/JavaTest/src/demo");
try {
//加载class文件
Class c = diskLoader.loadClass("demo.Hello");
if (c != null) {
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("sayHello", null);
//经过反射调用Hello类的sayHello方法
method.invoke(obj, null);
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException |
InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
复制代码
测试类按照预测结果应该打印:
say hello ------> classloader
复制代码
这行代码是咱们在 Hello 类中的一个方法 sayHello 里面所打印的。咱们运行一下上面的代码,查看下打印结果:
如料想的同样,打印出了正确结果。以上即是一个简单的自定义ClassLoader 类的实现过程。
以上即是关于 Java 中的 ClassLoader 里面的内容,还有一些暂未涉及到,接下来即是研究 Android中 的 ClassLoader 使用内容。
专一于 Android 开发多年,喜欢写 blog 记录总结学习经验,blog 同步更新于本人的公众号,欢迎你们关注,一块儿交流学习~