java中的.java文件经过编译以后,就会生成类文件.class文件。class文件是以二进制字节码存放在硬盘中的。当我们需要使用或加载Java文件到JVM中的时候,会从硬盘中读取字节码的class文件,然后通过类加载器将class文件加载到JVM中。也就是说,所有的Java文件都是通过类加载器加载到JVM中的。当然类加载器也是一个Java文件。那么第一个类加载器又是如何加载到JVM中的呢?在启动JVM的时候,会调运一个本地方法findBootStrapClass方法加载最初始的那个ClassLoader,private native Class findBootstrapClass(String name),这个本地方法使用C++编写的。
1.系统已有3种类加载器
1.1 BooststrapClassLoader(boot) 加载rt.jar下面的类(此加载器采用C++编写,一般开发中是看不到的)
1.2 ExtClassLoader 加载ExtClassLoader下面的类(ext文件夹下面的jar)
1.3 AppClassLoader 加载classpaht下面的类
我们写的类几乎都是通过AppClassLoader这个加载器加载到JVM中的。
2.类加载器的加载机制(双亲委托机制)
每一个类加载器都有一个对应的parentClassLoader。
2.1 系统类加载器的父子关系
自定义类加载器的父亲是AppClassLoader
AppClassLoader的父亲是ExtClassLoader
2.2 类加载器加载的顺序
类加载器是从根向下加载的
也就是boot-》ExiClassLoader -》AppClassLoader
当一个类需要被加载的时候,首先是由AppClassLoader加载器将其传递给其父亲ExtClassLoader,然后ExtClassLoader再将其传递给他的父亲boot。
当boot发现他的加载范围内有对应的class,就加载到JVM中,否则交给儿子ExtClassLoader处理。
ExtClassLoader再在他的加载范围类找有没有对应的class,有就加载到JVM中,没有就交给AppClassLoader处理。
AppClassLoader再在classpath路径下找对应的class,找到就加载,没有就报异常。
原因:这样可以保证JVM中某一个className对应的是同一个class,因为都是从根向下加载的。
避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
要是从下向上加载,可能导致某一个className在JVM中对应好几个class。可能我们会定义自己的类加载器和自己的加载范围。
当自己定义的类加载在他们各自的范围都发现需要加载的类,那么他们可能都会加载,导致JVM中一个className对应好几个不同的class
2.3 比如我们自己定义一个类加载器去加载java.lang.String这个类,那么我们是不能达到我们目的的。
因为加载机制是从上到下加载,当传递到上面的boot的时候,已经被加载到JVM中,轮不到我们自定义的类加载器去加载它。
但是,我们肯定是可以自己定义一个类加载器去加载我们指定的类的。
3.如何自定义一个类加载
首先,我们需要继承ClassLoadar
然后,我们不能破坏原来的类加载机制(双亲委托机制),所以我们不能覆盖loadClass方法,我们需要覆盖findclass方法。
最后,在findClass方法中写入我们的类加载器的代码。
查看源码解释:
4.举例
目标:自定义一个类加载器加我们指定路径下,经过我么加密的class。
过程:找到指定路径下的class文件,解密,加载到JVM中。
4.1先定义一个需要被加密编译的类,同时使用它进行测试
4.2加密原来的class文件
4.3编写我们自己的类加载器
4.4使用我们自己的类加载加载我们加密的类到JVM中
1.系统已有3种类加载器
1.1 BooststrapClassLoader(boot) 加载rt.jar下面的类(此加载器采用C++编写,一般开发中是看不到的)
1.2 ExtClassLoader 加载ExtClassLoader下面的类(ext文件夹下面的jar)
1.3 AppClassLoader 加载classpaht下面的类
我们写的类几乎都是通过AppClassLoader这个加载器加载到JVM中的。
2.类加载器的加载机制(双亲委托机制)
每一个类加载器都有一个对应的parentClassLoader。
2.1 系统类加载器的父子关系
自定义类加载器的父亲是AppClassLoader
AppClassLoader的父亲是ExtClassLoader
ExtClassLoader的父亲是BooststrapClassLoader
public class TestClassLoader {
public static void main(String[] args) {
// 当前对象的类加载器
ClassLoader loader = new TestClassLoader().getClass().getClassLoader();
// 从当前对象的类加载器想上找他的各个祖先
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
// 知道找到最后的祖先是null
System.out.println(loader);
}
}
输出:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
2.2 类加载器加载的顺序
类加载器是从根向下加载的
也就是boot-》ExiClassLoader -》AppClassLoader
当一个类需要被加载的时候,首先是由AppClassLoader加载器将其传递给其父亲ExtClassLoader,然后ExtClassLoader再将其传递给他的父亲boot。
当boot发现他的加载范围内有对应的class,就加载到JVM中,否则交给儿子ExtClassLoader处理。
ExtClassLoader再在他的加载范围类找有没有对应的class,有就加载到JVM中,没有就交给AppClassLoader处理。
AppClassLoader再在classpath路径下找对应的class,找到就加载,没有就报异常。
原因:这样可以保证JVM中某一个className对应的是同一个class,因为都是从根向下加载的。
避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
要是从下向上加载,可能导致某一个className在JVM中对应好几个class。可能我们会定义自己的类加载器和自己的加载范围。
当自己定义的类加载在他们各自的范围都发现需要加载的类,那么他们可能都会加载,导致JVM中一个className对应好几个不同的class
2.3 比如我们自己定义一个类加载器去加载java.lang.String这个类,那么我们是不能达到我们目的的。
因为加载机制是从上到下加载,当传递到上面的boot的时候,已经被加载到JVM中,轮不到我们自定义的类加载器去加载它。
但是,我们肯定是可以自己定义一个类加载器去加载我们指定的类的。
3.如何自定义一个类加载
首先,我们需要继承ClassLoadar
然后,我们不能破坏原来的类加载机制(双亲委托机制),所以我们不能覆盖loadClass方法,我们需要覆盖findclass方法。
最后,在findClass方法中写入我们的类加载器的代码。
查看源码解释:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查name对应的Class是否已经被加载
Class c = findLoadedClass(name);
//如果没有被加载
if (c == null) {
long t0 = System.nanoTime();
//尝试让parentClassLoader去加载
try {
if (parent != null) {
//当parent不为null的时候,让parent去loadClass
c = parent.loadClass(name, false);
} else {
//当parent为null的时候,就调运本地方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
//当parentClassLoader没有加载的时候
if (c == null) {
long t1 = System.nanoTime();
//调运findClass方法去加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
4.举例
目标:自定义一个类加载器加我们指定路径下,经过我么加密的class。
过程:找到指定路径下的class文件,解密,加载到JVM中。
4.1先定义一个需要被加密编译的类,同时使用它进行测试
public class MyClass extends Date {
@Override
public String toString() {
return "hello world";
}
}
4.2加密原来的class文件
public static void main(String[] args) throws Exception {
String inputSrc = args[0];//原来的class文件的路径和文件名
String outputSrc = args[1];//加密以后存放的文件路径和文件名
FileInputStream fis = new FileInputStream(inputSrc);
FileOutputStream fos = new FileOutputStream(outputSrc);
//调用加密算法
cypher(fis, fos);
fis.close();
fos.close();
}
/**
* 加密解密函数方法
*
* @param is
* @param os
* @throws IOException
*/
private static void cypher(InputStream is, OutputStream os) throws IOException {
int b = -1;
while ((b = is.read()) != -1) {
// 将0变成1,将1变成0
os.write(b ^ 0xff);
}
}
4.3编写我们自己的类加载器
public class MyClassLoader extends ClassLoader {
//要加载的类的路径
private String classSrc;
public MyClassLoader() {
}
public MyClassLoader(String classSrc) {
this.classSrc = classSrc;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//先找到自己的加密的class文件的位置
String classFielName = classSrc + "\\" + name + ".class";
try {
FileInputStream fis = new FileInputStream(classFielName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//调运解密算法
cypher(fis, baos);
fis.close();
byte[] bytes = baos.toByteArray();
//将读出来的二进制转换为Class字节码
return defineClass(null, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
4.4使用我们自己的类加载加载我们加密的类到JVM中
//首先使用自己的类加载器去加载我们加密的class文件
//注意,这个地方的加载类的路径下的class应该是我们加密以后文件的位置
Class clazz = new MyClassLoader("E:/AllWorkspace/workspace1/classLoaderTest/bin/com/gusi/test").loadClass("MyClass");
//通过反射,测试我们的classLoader
Date date = (Date) clazz.newInstance();
System.out.println(date.toString());