当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
简单的说,就是java 的JVM怎么把.class加载到内存中,就是ClassLoader 这哥们负责的
系统加载器AppClassLoader
瞅瞅这哥们源码,在哪呢,/sun/misc/Launcher.java
package sun.misc;
public class Launcher {
.......
static class AppClassLoader extends URLClassLoader {
.....
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
// Note: on bugid 4256530
// Prior implementations of this doPrivileged() block supplied
// a rather restrictive ACC via a call to the private method
// AppClassLoader.getContext(). This proved overly restrictive
// when loading classes. Specifically it prevent
// accessClassInPackage.sun.* grants from being honored.
//
return AccessController.doPrivileged(
new PrivilegedAction() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
}
看源码得知,AppClassLoader 加载的是系统属性"java.class.path"配置下类文件,也就是环境变量CLASS_PATH配置的路径。因此
我们可以知道,AppClassLoader是面向用户的加载器,我们平时写的代码和引用的第三方jar包,都是用它来加载的。
不信?走几步,瞧瞧,
public class TestClass {
public static void main(String[] args) {
ClassLoader cl = TestClass.class.getClassLoader();
System.out.println("===cl is=====>" + cl);
ClassLoader parent = cl.getParent();
System.out.println("===parent is=====>" + parent);
ClassLoader bootStrap = parent.getParent();
System.out.println("====bootStrap is ====>" + bootStrap);
}
}
来,走两步,看结果
===cl is=====>sun.misc.Launcher$AppClassLoader@4e25154f
===parent is=====>sun.misc.Launcher$ExtClassLoader@33909752
====bootStrap is ====>null
惊不惊喜意不意外,我们看到TestClass是被AppClassLoader加载进来的,而AppClassLoader的parent是ExtClassLoader。ExtClassLoader 的parent却为null。那我们看看,ExtClassLoader是个什么鬼
扩展类加载器ExtClassLoader
上源码,还是上面的路径/sun/misc/Launcher.java
package sun.misc;
public class Launcher {
..........
static class ExtClassLoader extends URLClassLoader {
...........
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
/*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}
.........
}
可以看到ExtClassLoader加载系统属性为"java.ext.dirs"下的文件,我们打印一下,看这属性下有哪些文件:
System.out.println("====dirs files ====>" + System.getProperty("java.ext.dirs"));
结果为:
====dirs files ====>/Users/hello/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
启动类加载器BootStrapClassLoader
BootstrapClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建ExtClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象
双亲委派模式
作为富家子弟,手里有三大宝贝,打妖怪的时候,JVM是如何知道使用哪个宝贝呢?
所谓双亲委派模式就是,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。
上阵父子兵,先父亲上,父亲搞不定,儿子再上(典型的坑爹类型),上源码
package java.lang;
public abstract class ClassLoader {
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;
}
}
}
我们可以看到第10行,判断是否加载完成,如果加载完成,直接返回c,如果首次加载,那c肯定为null,14行,判断parent是否为null,不为null,parent去加载。
有点乱哈,我们来理一理:
TestClass tc = new TestClass();
第一步:先AppClassLoader执行loadClass,走它的parent ExtClassLoader上,结果ExtClassLoader执行loadClass的时候,一想,哦,我自己虽然是AppClassLoader的parent,但是我还有我的parent BootStrapClassLoader.让他去执行。
第二步:BootStrapClassLoader执行的目录下找不到TestClass,又返回到ExtClassLoader自己
第三步:ExtClassLoader执行的目录下也没有找到TestClass,所以返回到AppClassLoader
第四步:爹坑不动,还是自己上,28行,执行 c = findClass(name);并加载到内存中
自定义ClassLoader
JVM自带的三个ClassLoader只能加载特定目录下的文件,如果想加载其他路径下的文件,比如磁盘下保存的.class文件(动态加载.class这就是热修复的基础)
1.新建一个类UserInfo
public class UserInfo {
public void printUserInfo(String name, Integer age) {
System.out.println("用户名:" + name + ";年龄:" + age);
}
}
2.新建一个类继承ClassLoader,重写findClass方法,并调用defineClass创建Class.
package com.whm.core;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class DiskClassLoader extends ClassLoader {
private String fileName;
public DiskClassLoader(String fileName) {
this.fileName = fileName;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
String newFileName = fileName + name + ".class";
byte[] classBytes = null;
Path path;
try {
path = Paths.get(new File(newFileName).toURI());
classBytes = Files.readAllBytes(path);
} catch (Exception e) {
e.printStackTrace();
}
return defineClass(name, classBytes, 0, classBytes.length);
}
}
3.TestClass类
package com.whm.core;
import java.lang.reflect.Method;
public class TestClass {
public static void main(String[] args) {
// TODO Auto-generated method stub
DiskClassLoader classLoader = new DiskClassLoader(
"/Users/hello/Desktop/eclipseProject/workspace/JavaProject/src/com/whm/core/");
try {
Class clazz = classLoader.loadClass("com.whm.core.UserInfo");
Method method = clazz.getDeclaredMethod("printUserInfo", String.class, Integer.class);
Object obj = clazz.newInstance();
method.invoke(obj, "张三", 20);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
文件路径、包名+类名、方法名、反射执行,看结果:
用户名:张三;年龄:20
搞定!!!