一:ClassLoader类加载器,主要的作用是将class文件加载到jvm虚拟机中。jvm启动的时候,并不是一次性加载所有的类,而是根据需要动态去加载类,主要分为隐式加载和显示加载。
隐式加载:程序代码中不通过调用ClassLoader来加载需要的类,而是通过JVM类自动加载需要的类到内存中。例如,当我们在类中继承或者引用某个类的时候,JVM在解析当前这个类的时,发现引用的类不在内存中,那么就会自动将这些类加载到内存中。
显示加载:代码中通过Class.forName(),this.getClass.getClassLoader.LoadClass(),自定义类加载器中的findClass()方法等。
二:jvm自带的加载器
(1)BootStrap ClassLoader:主要加载%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。可以通System.getProperty("sun.boot.class.path")
查看加载的路径,如下:
package test;
public class TestGC {
public static void main(String []args){
System.out.println(System.getProperty("sun.boot.class.path"));
}
}
显示结果如下:
D:\Program Files\Java\jdk1.7.0_45\jre\lib\resources.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\rt.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\sunrsasign.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jce.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.7.0_45\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.7.0_45\jre\classes
(2)Extention ClassLoader:主要加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。也可以通过System.out.println(System.getProperty("java.ext.dirs"))查看加载类文件的路径。
(3)AppClassLoader:主要加载当前应用下的classpath路径下的类。之前我们在环境变量中配置的classpath就是指定AppClassLoader的类加载路径。
三:类加载器的继承关系
先看一下这三个类加载器之间的继承关系,如下图:
ExtClassLoader,AppClassLoder继承URLClassLoader,而URLClassLoader继承ClassLoader,BoopStrap ClassLoder不在上图中,因为它是由C/C++编写的,它本身是虚拟机的一部分,并不是一个java类。jvm加载的顺序:BoopStrap ClassLoder-〉ExtClassLoader->AppClassLoder,下面看一段源码:
public class Launcher {
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
Thread.currentThread().setContextClassLoader(loader);
}
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {}
从源码中我们看到:(1)Launcher初始化的时候创建了ExtClassLoader以及AppClassLoader,并将ExtClassLoader实例传入到AppClassLoader中。
(2)虽然上一段源码中没见到创建BoopStrap ClassLoader,但是程序一开始就执行了System.getProperty("sun.boot.class.path")。
四:类加载器之间的父子关系
AppClassLoader的父加载器为ExtClassLoader,ExtClassLoader的父加载器为null,BoopStrap ClassLoader为顶级加载器。
下面一个小例子就可以证明,如下:新建一个Test类,可以通过getParent()方法获取上一层父机载器,执行如下代码:
package test;
public class TestGC {
public static void main(String []args){
System.out.println(Test.class.getClassLoader().toString());
System.out.println(Test.class.getClassLoader().getParent().toString());
System.out.println(Test.class.getClassLoader().getParent().getParent().toString());
}
}
输出结果如下:
五:类加载机制-双亲委托机制
例如:当jvm要加载Test.class的时候,
(1)首先会到自定义加载器中查找,看是否已经加载过,如果已经加载过,则返回字节码。
(2)如果自定义加载器没有加载过,则询问上一层加载器(即AppClassLoader)是否已经加载过Test.class。
(3)如果没有加载过,则询问上一层加载器(ExtClassLoader)是否已经加载过。
(4)如果没有加载过,则继续询问上一层加载(BoopStrap ClassLoader)是否已经加载过。
(5)如果BoopStrap ClassLoader依然没有加载过,则到自己指定类加载路径下("sun.boot.class.path")查看是否有Test.class字节码,有则返回,没有通
知下一层加载器ExtClassLoader到自己指定的类加载路径下(java.ext.dirs)查看。
(6)依次类推,最后到自定义类加载器指定的路径还没有找到Test.class字节码,则抛出异常ClassNotFoundException。如下图:
六:类加载过程的几个方法
(1)loadClass (2)findLoadedClass (3)findClass
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查是否已经加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空,调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则,调用Bootstrap Classloader
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();
//父加载器没有找到,则调用findclass
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()
resolveClass(c);
}
return c;
}
}
七:自定义类加载器步骤
(1)继承ClassLoader (2)重写findClass()方法 (3)调用defineClass()方法
下面写一个自定义类加载器:指定类加载路径在D盘下的lib文件夹下。
(1)新建一个Test.class类,代码如下:
package com.test;
public class Test {
public void say(){
System.out.println("Hello MyClassLoader");
}
}
(2)cmd控制台执行javac Test.java,将生成的Test.class文件放到D盘lib文件夹->com文件夹->test文件夹下。
(3)自定义类加载器,代码如下:
package test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader{
private String classpath;
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte [] classDate=getDate(name);
if(classDate==null){}
else{
//defineClass方法将字节码转化为类
return defineClass(name,classDate,0,classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//返回类的字节码
private byte[] getDate(String className) throws IOException{
InputStream in = null;
ByteArrayOutputStream out = null;
String path=classpath + File.separatorChar +
className.replace('.',File.separatorChar)+".class";
try {
in=new FileInputStream(path);
out=new ByteArrayOutputStream();
byte[] buffer=new byte[2048];
int len=0;
while((len=in.read(buffer))!=-1){
out.write(buffer,0,len);
}
return out.toByteArray();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
finally{
in.close();
out.close();
}
return null;
}
}
测试代码如下:
package test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestMyClassLoader {
public static void main(String []args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{
//自定义类加载器的加载路径
MyClassLoader myClassLoader=new MyClassLoader("D:\\lib");
//包名+类名
Class c=myClassLoader.loadClass("com.test.Test");
if(c!=null){
Object obj=c.newInstance();
Method method=c.getMethod("say", null);
method.invoke(obj, null);
System.out.println(c.getClassLoader().toString());
}
}
}
输出结果如下:
自定义类加载器的作用:jvm自带的三个加载器只能加载指定路径下的类字节码。如果某个情况下,我们需要加载应用程序之外的类文件呢?比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以使用自定义加载器了。