Java 类加载机制与对象实例化 已经阐明了 Java 中类加载器的种类,简单来说 Java 自带有三个类加载器,它们的具体作用就是根据需要去动态地将 class 文件加载到 JVM 虚拟机中去。这三个类加载器对应的实现如下:
- 启动类加载器,
Bootstrap ClassLoader
加载系统属性sun.boot.class.path
指定的路径下的类,它由C/C++编写,是虚拟机的一部分,无法在 java 代码中获知其实现- 扩展类加载器,
Extension ClassLoader
加载系统属性java.ext.dirs
指定的路径下的类,实现类为sun.misc.Launcher.ExtClassLoader
- 应用程序类加载器,
Application ClassLoader
加载环境变量ClassPath
中的类库,也就是系统属性java.class.path
指定的路径,实现类为sun.misc.Launcher.AppClassLoader
java.lang.ClassLoader
是 Java 层面对类加载器的抽象,这个类规范了类加载的基本流程,其中比较重要的属性及方法如下:
parent
:父类加载器的引用,一个类加载器通常都会保存一个父类加载器的引用,用于实现双亲委派机制loadClass()
方法,该方法为类加载器的核心方法,其中实现了双亲委派的逻辑
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
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;
}
}
sun.misc.Launcher.ExtClassLoader
是扩展类加载器的实现类,该类比较重要的方法如下:
getExtDirs()
方法中指定了其加载系统属性java.ext.dirs
指定的路径下的类- 其带参构造方法调用父类构造方法,传入的父类加载器参数明确指定为 null,也就是扩展类加载器没有 Java 层面的父类加载器
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();
}
}
void addExtURL(URL var1) {
super.addURL(var1);
}
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
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;
}
......
}
应用程序类加载器也叫系统类加载器,sun.misc.Launcher.AppClassLoader
是其实现类,该类比较重要的方法如下:
getAppClassLoader()
方法指定了其加载系统属性java.class.path
指定的路径下的类- 重写的
loadClass()
方法做了加载 class 文件的一个优化,如果某个类存在于缓存目录但是又不在内存中,则直接由其进行连接
,否则还是需要交给父类加载器去加载
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);
}
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);
}
}
......
}
上图是一个可能的类加载流程,流程中不少重要的步骤是 native
方法实现,在 Java 层无法得知细节。Java 层加载类的核心方法代码如下,简单描述的话主要是以下几个步骤:
- 一个
AppClassLoader
加载类时,首先查看该类是否已经被加载过,有则从缓存中获取,否则需要委托给父类加载器去处理ExtClassLoader
处理流程和AppClassLoader
完全一致,如果它也没有加载过目标类,则由Bootstrap ClassLoader
去执行加载逻辑,也就是在sun.mic.boot.class
配置的路径去目标类加载,加载完成就返回,没有加载到就让子加载器自己去找Bootstrap ClassLoader
如果没有成功加载到目标类,则ExtClassLoader
自己在java.ext.dirs
配置的路径中去查找目标类加载,成功就返回,不成功再向下让子加载器去加载类ExtClassLoader
加载类不成功,AppClassLoader
在java.class.path
配置的路径下查找目标类,找到就加载返回。如果没有找到接着让子类加载器找,如果没有子类就会抛出各种异常
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); // 1.首先判断类是否已经被加载
if (c == null) { // 2. 未被加载则进行双亲委派加载
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) { //3.父类加载器 loadClass 未加载到类才调用 findClass
// 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;
}
}