JVM为我们提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到JVM的内存中,并且严格遵守着父委托的机制。
根加载器又称为Bootstarp类加载器,该类加载器是最为顶层的加载器,其没有父加载器,它是由C++编写的,主要负责虚拟机核心类库的加载,比如整个java.lang包都是由根加载器所加载的。根类加载器从系统属性sun.boot.class.path所指定的目录加载类库。
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));
}
打印结果:
C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\classes
…
扩展类加载器的父加载器是根加载器。它从java.ext.dirs系统属性所指定的目录下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader的子类
public static void main(String[] args) {
System.out.println(System.getProperty("java.ext.dirs"));
}
打印结果:
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
系统加载器又称为应用加载器,它的父加载器是扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录下加载类。它是用户自定义的类加载器的默认父加载器,系统加载器是纯Java类,是java.lang.ClassLoader的子类。每一个类的构造中都会有对应ClassLoader的引用。
public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
//反射构造某个类
Class<?> klass = Class.forName("com.Reyco.MyThread.Singleton");
System.out.println(klass.getClassLoader());
System.out.println(klass.getClassLoader().getParent());
}
打印结果:
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
父子加载器并非继承关系,子加载器不一定继承了父加载器,两者之间是种包装关系。
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层级的类加载都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
当我们自定义了java.lang.String试图引发原生java.lang.String的冲突时,很遗憾我们无法达成此目的。当我们尝试加载自定义的java.lang.String类时,系统加载器将请求委托交给父类加载器直到传递到根类加载器,而根类加载器的加载路径下有原生的java.lang.String,所以并不会加载我们自定义的String类。
父类加载器的优点:提高系统安全性,在此机制下,用户自定义的类加载器不可能加载父加载器本应加载的可靠类,可以防止恶意代码代替父类加载器的可靠代码
打开ClassLoader源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//本地方法,检查这个class是否已经被加载
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
}
//如果此时仍然未加载成功,则执行findClass()方法
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//ClassLoader下实现的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(c);
}
return c;
}
}
所有的自定义类加载器都是ClassLoader的直接子类或间接子类,同时需要实现findClass方法,否则将会抛出Class找不到的异常。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
public class DiyClassLoader extends ClassLoader {
//默认加载目录
private final static String DEFAYLT_DIR="D:\\ClassLoader";
private String dir=DEFAYLT_DIR;
private String classLoaderName;
public DiyClassLoader() {
super();
}
public DiyClassLoader(String classloadername) {
super();
this.classLoaderName=classloadername;
}
public DiyClassLoader (String classloadername,ClassLoader parent){
super(parent);
this.classLoaderName=classloadername;
}
public void setDir(String dir) {
this.dir=dir;
}
public String getDir() {
return this.dir;
}
public String getClassLoaderName() {
return this.classLoaderName;
}
//重写findClass
//按照父委托机制,先将委托请求层层提交到父加载器下,若是找不到则执行findClass()方法
//自定义MyObject路径不在JVM内置的三大类加载器下
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//将"."替换成"/"
String classpath = name.replace(".","/");
File classFile = new File(dir,classpath+".class");
if(!classFile.exists()) {
throw new ClassNotFoundException("文件找不到");
}
byte[] classBytes = loadClassBytes(classFile);
if(null==classBytes || classBytes.length==0) {
throw new ClassNotFoundException("加载失败");
}
//返回class
return this.defineClass(name, classBytes,0,classBytes.length);
}
private byte[] loadClassBytes(File classfile) {
try {
//文件数组输出流
ByteArrayOutputStream baos =new ByteArrayOutputStream();
FileInputStream file = new FileInputStream(classfile);
byte[] buffer = new byte[1024];
int len=0;
while((len=file.read(buffer))!=-1) {
baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
}catch(Exception e) {
return null;
}
}
}
测试类:
之前自定义类Myobject
public static void main(String[] args) throws InterruptedException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
DiyClassLoader diyClassLoader = new DiyClassLoader("MyClassLoader");
Class<?> klass=diyClassLoader.loadClass("com.Reyco.MyThread.MyObject");
System.out.println(klass);
System.out.println(klass.getClassLoader());
//会调用MyObject的静态代码块“成功加载MyObjcey类”
Object obj = klass.newInstance();
//执行MyObject中的hello()方法
Method method = klass.getMethod("hello",new Class<?>[] {});
Object string=method.invoke(obj,new Object[] {});
System.out.println(string);
}
打印结果:
class com.Reyco.MyThread.MyObject
com.Reyco.MyThread.DiyClassLoader@6d06d69c
成功加载MyObject类
Hello World
JDK提供的双亲委托机制并非强制性模型,我们可以对其进行覆写,实现父委托机制的打破。
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> klass = null;
//核心类包仍然交给内置类加载器加载
if(name.startsWith("java.")) {
try {
ClassLoader system = ClassLoader.getSystemClassLoader();
klass=system.loadClass(name);
if(klass!=null) {
if(resolve) {
resolveClass(klass);
}else {
return klass;
}
}
}catch(Exception e) {
e.printStackTrace();
}
}
//自己的类直接进行findClass,避过父委托机制
//实现父委托机制的打破
try {
klass=findClass(name);
}catch(Exception e) {
e.printStackTrace();
}
if(klass==null && getParent()!=null) {
getParent().loadClass(name);
}
return klass;
}
类加载器的命名空间
注意:
public static void main(String[] args) throws InterruptedException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
DiyClassLoader diyClassLoader1 = new DiyClassLoader("MyClassLoader1");
DiyClassLoader2 diyClassLoader2 = new DiyClassLoader2("MyClassLoader2");
Class<?> klass1=diyClassLoader1.loadClass("com.Reyco.MyThread.MyObject");
Class<?> klass2=diyClassLoader2.loadClass("com.Reyco.MyThread.MyObject");
System.out.println(klass1.hashCode());
System.out.println(klass2.hashCode());
}
打印结果:
865113938
1442407170
我们发现哈希码不同,这两个clas对象是不同的。
在类加载器进行类加载时,首先会到记录表也就是缓存中,查看该类是否已经被加载,如已经加载过了,就不会重复加载,否则被认为是首次加载。
我们在编写代码时通常会给一个类指定一个包名,包的作用是组织类,防止不同包下同样名称的class引起冲突,包名和类名构成了类的全限定名。在JVM运行时class有一个运行时包,运行时的包时由类加载器的命名空间和类的全限定名称构成的。