在Java虚拟机中加载类包括三个步骤。 链接,初始化。 其中,加载是查找字节流(即由Java编译器生成的类文件)并据此创建类的过程,需要在中途使用类加载器查找字节流。
Java虚拟机的默认类加载器
Java虚拟机提供了三种类型的加载器。 引导类加载器、扩展类加载器和APP应用程序类加载器。 除了引导类加载器之外,所有类加载器都是java.lang.ClassLoader的子类。/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包都位于内存中扩展类加载器是sun.misc.launcher $ ext class loader类,用Java语言实现,是launcher的静态内部类。 /lib/ext目录或系统变量-加载由-Djava.ext.dir指定的位路径中的类库。 父类加载器是null.APP应用程序类加载器,用于加载APP应用程序路径下的类。 此处的路径
请注意,其中的父子类加载器不是继承关系,而是ClassLoader类的parent属性。 让我们看看在Launcher类中创建扩展类加载器的代码。
publicextclassloader (文件[ ] var1) throws IOException { )。
super(getextUrls(var1),ClassLoader ) null,Launcher.factory );
SharedSecrets.getJavaNetAccess ().geturl class path (this ) ).initlookupcache (this );
}
此处父加载器设置为null。
父母委托机制
加载类时,Java虚拟机默认采用父母委托机制。 也就是说,当一个类加载器接收到加载请求时,请求将转发到父类加载器,如果父类加载器在路径下找不到该类,则将其加载到子类加载器中。 让我们看看ClassLoader的laodClass方法。
protectedclassloadclass (字符串名称,布尔资源) )。
throws类ssnotfoundexception
{
已同步(获取分类加载锁定(name ) )。
//首先判断是否加载了类,加载后直接返回
classc=findloadedclass(name;
if(c==null ) {
long t0=System.nanoTime (;
try {
if(Parent!=空) {
//有父类加载器,调用父加载器的loadClass
c=parent.loadclass(name,false );
} else {
调用bootstrap类加载器
c=findbootstrapclassornull (name;
}
}catch(classnotfoundexceptione ) )
//classnotfoundexceptionthrownifclassnotfound
//from the non-nullparentclassloader
}
if(c==null ) {
long t1=System.nanoTime (;
//查找自己指定的类的加载路径下是否有class字节码
c=查找类(name );
//this is the defining class loader; 记录状态
sun.misc.perfcounter.getparentdelegationtime ().addtime ) T1-T0 );
sun.misc.perfcounter.getfindclasstime ().addelapsedtimefrom ) ) T1;
sun.misc.perfcounter.getfindclasses ().increment );
}
}
解析(if )
解决类(c;
}
返回c;
}
}
在此级别,可以避免类的重复加载。 如果父亲已经加载了类,则不需要再次加载子类加载器。 接下来,我们还考虑了安全因素,例如自己编写java.lang.String类并将其委托给父母
机制传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载我们新写的java.lang.String,而直接返回已加载过的String.class,这样保证生成的对象是同一种类型.自定义类加载器
除了jvm自身提供的类加载器,我们还可以自定义类加载器,我们先写一个Person类
public class Person {
private int age;
private String name;
//省略getter/setter方法
}
我们先看他是由哪个类加载器加载的.
public class TestJava {
public static void main(String[] args) throws Exception {
Person person = new Person();
System.out.println("person是由" + person.getClass().getClassLoader() + "加载的");
}
}
运行结果如下:
我们把Person.class放置在其他目录下
再运行会发生什么,在上面的loadClass方法中其实已经有了答案,会抛出ClassNotFoundException,因为在指定路径下查找不到字节码.
我们现在写一个自定义的类加载器,让他能够去加载person类,很简单,我们只需要继承ClassLoader并重写findClass方法,这里面写查找字节码的逻辑.
public class PersonCustomClassLoader extends ClassLoader {
private String classPath;
public PersonCustomClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
我们来测试一下:
public class TestJava {
public static void main(String[] args) throws Exception {
PersonCustomClassLoader classLoader = new PersonCustomClassLoader("/home/shenxinjian");
Class> pClass = classLoader.loadClass("me.shenxinjian.algorithm.Person");
System.out.println("person是由" + pClass.getClassLoader() + "类加载器加载的");
}
}
测试结果如下:
编写自定义类加载器的意义
当class文件不在classPath路径下,如上面那种情况,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的classLoader来加载特定路径下的class文件来生成class对象。
当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑