一、定义和作用
ClassLoader是Java的核心组件,主要工作在Class装载的加载阶段。所有的Class都是由ClassLoader进行加载的,ClassLoader 负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
二、分类
1.BootStrapClassLoader
作用:C++编写而成, 它是最顶层的类加载器,已经内嵌到JVM中了。在JVM启动时会初始化该ClassLoader,它主要用来读取Java的核心类库JRE/lib/rt.jar中所有的class文件,这个jar文件中包含了java规范定义的所有接口及实现。
2.ExtClassLoader
(1)作用
Java编写,它是用来加载Java的扩展类库javax.*,如读取JRE/lib/ext/*.jar中的包等(这里要注意,有些版本的是没有ext这个目录的)。
(2)源码-》查看加载文件路径:
static class ExtClassLoader extends URLClassLoader {
…
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
…
}
Idea中查看主要类目录
public class test {
public static void main(String[] args) {
System.out.println(System.getProperty("java.ext.dirs"));
}
}
结果:
C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
3.AppClassLoader
(1)作用
Java编写,它是用来读取CLASSPATH下指定的所有jar包或目录的类文件,一般情况下这个就是程序中默认的类加载器。
(2)源码-》查看加载文件路径:
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
}
文件加载路径为:System.getProperty("java.class.path")
package com.spring.ioc.c5;
public class test {
public static void main(String[] args) {
System.out.println(System.getProperty("java.class.path"));
}
}
路径:主要是...\javabase\target\classes下面的编译好的包和类
C:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program…
E:\02LocalProject\javabase\target\classes;
….
4.自定义ClassLoader
定制化开发,下面可以代码实现
5.Loader的双亲委派机制
(1)作用:可以避免同样的字节码加载多次到内存中,一次加载到内存中,如果后续需要,则直接取出使用,不需要,重复加载第二次。
(2)自下而上,检测是否已经加载,如果加载过,则直接调用该class;如果没有加载过,则采用自下而上重新加载。
(3)自上而下,如果寻找对应class文件,有则加载
(4)源码
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关键方法以及层次关系
1. loadClass
作用:ClassLoader加载类的入口,此方法负责加载指定名字的类。ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找。
2.findClass
作用:查找二进制class文件,并且加载返回一个对象
源码:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
3. defineClass
作用:读取字节流,返回类对象
源码:
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
4.三者关系
四、自定义classloader代码
1.写一个测试类InputClass.java
package com.spring.ioc.c5;
public class InputClass {
static {
System.out.println("Hello World! I am InputClass");
}
}
使用idea的终端Terminal在当前目录下编译为InputClass.class文件
…\javabase\src\main\java\com\spring\ioc\c5>javac InputClass.java
生成…\javabase\src\main\java\com\spring\ioc\c5\InputClass.class
2.实现自定义加载类ClassLoaderDIY.java
package com.spring.ioc.c5;
import java.io.*;
public class ClassLoaderDIY extends ClassLoader{
private String path; //加载class的路径(不包含文件名称)
private String classLoaderName; //加载的class名称
public ClassLoaderDIY(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
//寻找类文件,并且以二进制数组方式加载进来
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b=loadClassData(name);
return defineClass(name,b,0,b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name=path+name+".class"; //即:路径+名称
InputStream in=null;
ByteArrayOutputStream out=null;
try {
in=new FileInputStream(new File(name));
out=new ByteArrayOutputStream();
int i=0;
while ((i=in.read())!=-1){
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
}
3.测试类ClassLoadTest.java
package com.spring.ioc.c5;
public class ClassLoadTest {
public static void main(String[] args) {
ClassLoaderDIY classLoaderDIY=new ClassLoaderDIY("src\\main\\java\\com\\spring\\ioc\\c5\\","FirstClassLoader");
Class c= null;
try {
c = classLoaderDIY.loadClass("com.spring.ioc.c5.InputClass");
System.out.println(c.getClassLoader());
c.newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意:因为加载类,在com.spring.ioc.c5下面,所以,加载类时需要加载指定包目录:com.spring.ioc.c5.InputClass
4.测试结果
加载成功
sun.misc.Launcher$AppClassLoader@18b4aac2
Hello World! I am InputClass