java中类加载器入门
前言
顾名思义,类加载器就是负责类的加载的,从内存中,class文件中,jar包中等渠道加载,对于任意一个class,都需要由加载它的类加载器和这个类本身确定其在JVM中的唯一性。
java内置类加载器
java中内置了3种类加载器
根类加载器
又叫做Bootstrap类加载器,是最顶层的类加载器,没有父类加载器,是C++语言编写的,主要负责java中核心类库的加载,如java.lang包下所有类。
public class Test {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
String property = System.getProperty("sun.boot.class.path");
for (String path : property.split(";")) {
System.out.println(path);
}
}
}
输出为
null
D:\java\jdk\jdk1.8.0_181\jre\lib\resources.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\rt.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\sunrsasign.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jsse.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jce.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\charsets.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jfr.jar
D:\java\jdk\jdk1.8.0_181\jre\classes
根类加载器获取不到引用,所以为null,常用的类如String都是在rt.jar这个jar中的。
扩展类加载器
由java语言实现,父类加载器为根类加载器,主要加载JAVA_HOME下jre\lib\ext下的目录
import jdk.nashorn.tools.Shell;
public class Test {
public static void main(String[] args) {
System.out.println(Shell.class.getClassLoader());
String property = System.getProperty("java.ext.dirs");
for (String path : property.split(";")) {
System.out.println(path);
}
}
}
输出为
sun.misc.Launcher$ExtClassLoader@6d6f6e28
D:\java\jdk\jdk1.8.0_181\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
Shell类就是nashorn.jar中的一个类,类加载器确实为扩展类加载器。
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.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;
}
}
扩展类加载器的定义如上,使用了双重锁判断的单例写法。
系统类加载器
也是java语言实现的,父类加载器为扩展类加载器,加载classpath下的类,如我们引入的第三方jar包和我们自己定义的类。
public class Test {
public static void main(String[] args) {
System.out.println(Test.class.getClassLoader());
String property = System.getProperty("java.class.path");
for (String path : property.split(";")) {
System.out.println(path);
}
}
}
输出为
sun.misc.Launcher$AppClassLoader@18b4aac2
D:\java\jdk\jdk1.8.0_181\jre\lib\charsets.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\deploy.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\cldrdata.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\dnsns.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\jaccess.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\jfxrt.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\localedata.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\nashorn.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunec.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\zipfs.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\javaws.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jce.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jfr.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jfxswt.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\jsse.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\management-agent.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\plugin.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\resources.jar
D:\java\jdk\jdk1.8.0_181\jre\lib\rt.jar
C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar
系统类加载器的定义
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
先创建扩展类加载器,将扩展类加载器作为父类加载器创建系统类加载器,并设置到当前线程的上下文类加载器中。
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);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
类加载流程
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;
}
}
- 首先检查该类是否已经加载过,如果是,直接返回已经加载过的类
- 父类加载器存在,委托父类加载器加载,不存在,委托根类加载器
- 都不能加载的话,由当前类加载器加载,重写findClass()方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
URLClassLoader就是通过重写findClass()实现的。
打破双亲委派机制
双亲委派机制保证了越基础的类由越上层的类加载器加载,基础类之所以被称为“基础”,是因为它们总是作为被调用的API。但是,如果基础类要调用用户的代码,那该怎么办呢。一个典型的例子就是JDBC
jdk提供了所有相关的接口,这些接口都是由根类加载器加载的,而第三方驱动的实现是由系统类加载器加载的,按照双亲委派机制,根类加载器不可能加载得到第三方驱动的实现,为了解决这个困境,java引入了一种不太优雅的设计,线程上下文类加载器,这就变成了父类加载器委派子类加载器去加载,打破了双亲委派机制。java中所有涉及到SPI的动作基本上都是采用的这种方式。