一、ClassLoader简介
Java的类加载器的作用就是在运行时加载类到虚拟机中,首先不管对于什么样的java应用肯定是由很多class组成实现的,不同的功能所在的class是不一样的(你说我把所有的功能放在一个class里面,那你很棒棒哦),比如当我们的入口函数被调用的时候,入口函数使用了其他class(静态代码块、实例)的功能,这时类加载器就会按需加载将需要的类加载进内存中,类加载器不是一次性全部class加载到内存中,而是按需加载。类加载器加载class的原则是:委托、可见性和单一性。
单一性:单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
可见性:可见性是指子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类 。
委托:这里重点说下双亲委托机制,说这个之前我们先看下jvm定义的类加载器:
Bootstrap ClassLoader: 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等等。另:我们是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。
Extention ClassLoader: 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
AppclassLoader: 也称为SystemAppClass 加载当前应用的classpath的所有类。
除了以上3个jvm定义的类加载器,我们也可以自定义类加载器(通过继承ClassLoader这里就不说了)。这里的父类加载器不是继承关系,而是持有一个应用!类的加载流程用一句话简单说就是向上委托,向下查找加载。流程如下:
二、类加载器的委托加载
看了这3个类加载器加载路径和执行顺序后,为了更好的理解,我们从代码方面看下具体的加载路径和加载顺序。我们先看下sun.misc.Launcher,它是java程序的入口,这个类的代码结构如下:
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);
}
//设置AppClassLoader为线程上下文类加载器
Thread.currentThread().setContextClassLoader(loader);
}
static class AppClassLoader extends URLClassLoader { }
static class ExtClassLoader extends URLClassLoader{}
public ClassLoader getClassLoader() {
return this.loader;
}
从代码看出,Launcher在构造器中初始化了ExtClassLoader和AppClassLoader,但是没有Bootstrap类加载器的信息。但是有个路径:sun.boot.class.path,这就是BootstrapClassLoader加载jar的路径,我们看下BootstrapClassLoader加载哪些jar或classes:
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));
}
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/classes
BootstrapClassLoader因为不是Java写的类加载器,所以在这里是看不到的,但是它也是要去加载类的,通过BootClassPathHolder类可以发现其加载范围是由System.getProperty("sun.boot.class.path")决定的。上面我们也看到了其加载的路径了。
ExtClassLoader类加载器,我们首先看下ExtClassLoader的代码:
static class ExtClassLoader extends URLClassLoader {
//The class loader used for loading installed extensions.
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
final File[] var0 = getExtDirs();
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
}
//获取加载的路径
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;
}
static {
ClassLoader.registerAsParallelCapable();
}
.....省略
}
这里我们又看到了一个路径 java.ext.dirs,这个是ExtClassLoader的加载路径,我们看下它是加载了哪些路径:
public static void main(String[] args) {
System.out.println(System.getProperty("java.ext.dirs"));
}
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/ext
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
再看看AppClassLoader部分,接着看它的代码部分:
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
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);
}
});
}
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
static {
ClassLoader.registerAsParallelCapable();
}
}
可以看到AppClassLoader加载的就是java.class.path
下的路径。我们同样打印它的值。
public static void main(String[] args) {
System.out.println(System.getProperty("java.class.path"));
}
//这里打印出来很多的,我这里只是粘贴出来我们当前运行的这个工程
/Users/pengjian/work/workspace/pepsi/pepsi-master/tomcat-demo/target/classes
现在3个类加载器已知晓其加载的路径了,下面我们用事例来看下它的加载顺序是什么。先看一个简单的代码片段:
public class SimpleClass {
}
public class ClassLoaderTest {
public static void main(String[] args) {
//当前类加载器
ClassLoader cl = SimpleClass.class.getClassLoader();
System.out.println(cl);
//父类加载器
ClassLoader parentCLoader= cl.getParent();
System.out.println(parentCLoader);
//父类的父类
ClassLoader grandParentCl= parentCLoader.getParent();
System.out.println(grandParentCl);
}
}
输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@23fc625e
null
这里不难看出,当前SimpleClass是我的应用中的类,是应该有AppClassLoader加载,通过输出也可以看出,其AppClassLoader的父类加载器是ExtClassLoader。但是为什么ExtClassLoader的父类是null呢?我们看下Launcher中的代码:
ClassLoader extcl;
//没有设置父类
extcl = ExtClassLoader.getExtClassLoader();
//设置父类加载器是extcl
loader = AppClassLoader.getAppClassLoader(extcl);
这里先说下AppClassLoader和ExtClassLoader,有个父类URLClassLoader,ExtClassLoader在调用 父类构造方法的时候传入的父类加载器为null,所以我们这里输出出来的ExtClassLoader父类加载器为null。可能有人要问了,既然是null为什么可以当作ExtClassLoader的父类加载器去加载java_home/lib下面的jar和classes?
因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用。
我们在从代码层面去看当需要Bootstrap ClassLoader加载的时候是怎么loadClass的,先看下loadClass方法代码:
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;
}
}
这段代码基本上解释了上面所有的问题。
1、先是执行findLoadedClass(String)去检测这个class是不是已经加载过了。
2、执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器,这里最终加载方法是一个本地方法。
3、如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
总结一下类加载器:
1、java 定义了3个ClassLoader,用于加载不同路径的class类。
2、ClassLoader通过双亲委托来按需加载指定路径下的class和资源。
3、另外,JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。
下面一篇我们将说明一下自定义类加载器和tomcat的类加载器。