ClassLoader
ClassLoader初始化
ClassLoader
入口:sun.misc.Launcher
public class Launcher {
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
...
// Create the extension class loader
ClassLoader extcl = ExtClassLoader.getExtClassLoader();
// Create class loader used to launch the main application.
loader = AppClassLoader.getAppClassLoader(extcl);
//设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
Thread.currentThread().setContextClassLoader(loader);
...
}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {
}
ExtClassLoader
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
...
instance = createExtClassLoader();
...
return instance;
}
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
...
// -Djava.ext.dirs=./plugin:$JAVA_HOME/jre/lib/ext
// 获取java.ext.dirs对应的的所有目录,java.ext.dirs没配置则为空
// File[] var1 到 xxx.jar 这一层
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
...
// 注册、保存 File[] var1(每个File就是一个jar文件) 中的 每个File对应的 package路径信息
// package路径信息具体就是: com/sun/image/ 这样
// 建立 Map(new File(jarFile,jarName), new MetaIndex(List<jar中的package路径名称>, jar中是否只有class文件))
MetaIndex.registerDirectory(var1[i]);
...
// 初始化、缓存 File[] var1 涉及到的url
return new Launcher.ExtClassLoader(var1);
}
}
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;
}
// 解析每个jar,并建立映射
public static synchronized void registerDirectory(File jarFileDir) {
// 在 jarFileDir 目录下 拷贝生成 meta-index文件
// new File(file ,child ): 根据file 抽象路径名和 child 路径名字符串创建一个新 File 实例
File jarFile= new File(jarFileDir, "meta-index");
if (var1.exists()) {
try {
BufferedReader jarFileBufferedReader = new BufferedReader(new FileReader(jarFile));
String lineStr = null;
String jarName = null;
boolean isOnlyHasClassName = false;
ArrayList list = new ArrayList();
Map jarMap = getJarMap();
jarFile = jarFile.getCanonicalFile();
lineStr = jarFileBufferedReader.readLine();
if (lineStr == null || !lineStr.equals("% VERSION 2")) {
jarFileBufferedReader.close();
return;
}
while((lineStr = jarFileBufferedReader.readLine()) != null) {
switch(lineStr.charAt(0)) {
// 对于仅包含类文件的jar文件,我们输入’!'在jar文件名之前
case '!':
// 对于包含资源和类文件的jar文件,我们在jar名称前加上’#’
case '#':
// 对于只包含资源文件的jar文件,我们在jar文件名前加上’@’;
case '@':
if (jarName != null && list.size() > 0) {
jarMap.put(new File(jarFile, jarName), new MetaIndex(list, isOnlyHasClassName));
list.clear();
}
jarName = lineStr.substring(2);
if (lineStr.charAt(0) == '!') {
isOnlyHasClassName = true;
} else if (isOnlyHasClassName) {
isOnlyHasClassName = false;
}
case '%':
break;
default:
list.add(lineStr);
}
}
if (jarName != null && list.size() > 0) {
jarMap.put(new File(jarFile, jarName), new MetaIndex(list, isOnlyHasClassName));
}
jarFileBufferedReader.close();
} catch (IOException var8) {
}
}
}
每个jar文件至少包含清单文件,因此当我们说“只包含类文件的jar文件”时,我们不包含该文件。
meta-index文件如下,
% VERSION 2
% WARNING: this file is auto-generated; do not edit
% UNSUPPORTED: this file and its format may change and/or
% may be removed in a future release
# charsets.jar
sun/nio
sun/awt
# jce.jar
javax/crypto
sun/security
META-INF/ORACLE_J.RSA
META-INF/ORACLE_J.SF
# jfr.jar
oracle/jrockit/
jdk/jfr
com/oracle/jrockit/
! jsse.jar
sun/security
com/sun/net/
! management-agent.jar
@ resources.jar
com/sun/java/util/jar/pack/
META-INF/services/sun.util.spi.XmlPropertiesProvider
META-INF/services/javax.print.PrintServiceLookup
com/sun/corba/
META-INF/services/javax.sound.midi.spi.SoundbankReader
sun/print
META-INF/services/javax.sound.midi.spi.MidiFileReader
META-INF/services/sun.java2d.cmm.CMMServiceProvider
javax/swing
META-INF/services/javax.sound.sampled.spi.AudioFileReader
META-INF/services/javax.sound.midi.spi.MidiDeviceProvider
sun/net
META-INF/services/javax.sound.sampled.spi.AudioFileWriter
com/sun/imageio/
META-INF/services/sun.java2d.pipe.RenderingEngine
META-INF/mimetypes.default
META-INF/services/javax.sound.midi.spi.MidiFileWriter
sun/rmi
javax/sql
META-INF/services/com.sun.tools.internal.ws.wscompile.Plugin
com/sun/rowset/
META-INF/services/javax.print.StreamPrintServiceFactory
META-INF/mailcap.default
java/lang
sun/text
javax/xml
META-INF/services/javax.sound.sampled.spi.MixerProvider
com/sun/xml/
META-INF/services/com.sun.tools.internal.xjc.Plugin
com/sun/java/swing/
com/sun/jndi/
com/sun/org/
META-INF/services/javax.sound.sampled.spi.FormatConversionProvider
! rt.jar
com/sun/java/util/jar/pack/
java/
org/ietf/
com/sun/beans/
com/sun/tracing/
com/sun/java/browser/
com/sun/corba/
com/sun/media/
com/sun/awt/
com/sun/management/
sun/
com/sun/jmx/
com/sun/demo/
com/sun/imageio/
com/sun/net/
com/sun/rmi/
org/w3c/
com/sun/swing/
com/sun/activation/
com/sun/nio/
com/sun/rowset/
org/jcp/
com/sun/istack/
jdk/
com/sun/naming/
org/xml/
org/omg/
com/sun/security/
com/sun/image/
com/sun/xml/
com/sun/java/swing/
com/oracle/
com/sun/java_cup/
com/sun/jndi/
com/sun/accessibility/
com/sun/org/
javax/
public ExtClassLoader(File[] var1) throws IOException {
// 初始化URLClassPath(内部封装了var1对应的所有的url)
// 设置parent classLoader、初始化 并行加载涉及到的parallelLockMap容器
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
// initLookupCache(this):初始化URl缓存
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
// urls: the URLs from which to load classes and resources
// parent: the parent class loader for delegation
// factory: the URLStreamHandlerFactory to use when creating URLs
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
// 设置父类加载器, 初始化 并行加载涉及到的parallelLockMap容器
super(parent);
...
// 封装urls
ucp = new URLClassPath(urls, factory, acc);
}
// class ClassLoader
private ClassLoader(Void unused, ClassLoader parent) {
// 设置父类加载器
this.parent = parent;
// 如果开启了并行加载能力(本质上是成功loaderTypes.add(classLoader) ), 则 parallelLockMap = new ConcurrentHashMap<>();
// ParallelLoaders.isRegistered(classLoader) == true
// 实际上就是判断 loaderTypes.contains(classLoader)
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
...
} else {
parallelLockMap = null;
...
}
}
// URLClassPath
synchronized void initLookupCache(ClassLoader var1) {
// getLookupCacheURLs(): 本地方法,初始化URL缓存
if ((this.lookupCacheURLs = getLookupCacheURLs(var1)) != null) {
this.lookupCacheLoader = var1;
} else {
disableAllLookupCaches();
}
}
以上是 ExtClassLoader的初始化
AppClassLoader
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
// 获取设置的 java.class.path
final String var1 = System.getProperty("java.class.path");
// java.class.path的目录路径 ==> File[]
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
···
// File[] ==> URL[]
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
// 初始化 URL[] var1x 下的url缓存,并设置 parentClassLoader
return new Launcher.AppClassLoader(var1x, var0);
}
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
static {
ClassLoader.registerAsParallelCapable();
}
}
并行化加载能力
public class URLClassLoader {
static {
...
// 开启 并行加载能力(本质上是loaderTypes.add(classLoader) )
// 本质上,就是 loadClass时,将 synchronized 的范围 由 当前classLoader 改为 当前要加载的class
ClassLoader.registerAsParallelCapable();
}
}
关于ClassLoader.registerAsParallelCapable();
(并行加载):
JDK6
上Classloader.loadClass(String name)
这个方法是synchonized的,如果应用里面有多个线程在同时调用loadClass方法进行类加载的话,那么锁的竞争将会非常激烈。
在JDK7
上,如果调用Classloader.registerAsParallelCapable
方法,则会开启并行类加载功能,把锁的级别从ClassLoader
对象本身,降低为要加载的类名
这个级别。换句话说只要多线程加载的不是同一个类的话,loadClass方法都不会锁住。
// ClassLoader
protected static boolean registerAsParallelCapable() {
// 为当前加载器注册并行能力
return ParallelLoaders.register(callerClass);
}
// ClassLoader
static boolean register(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
// loaderTypes(是个set) 如果包含 当前classLoader的超类,就再次add当前classLoader,并返回true,代表注册成功
// 核心保证:如果一个classLoader想开启并行加载,必须保证
// 这条加载器链路(当前classLoader 和 他的父亲、祖先 classLaoder 构成)
// 上的所有classLoader都开启了并行加载能力
// loaderTypes静态代码块初始化了: loaderTypes.add(ClassLoader.class);
if (loaderTypes.contains(c.getSuperclass())) {
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
// ClassLoader
// the set of parallel capable loader types
private static final Set<Class<? extends ClassLoader>> loaderTypes =
Collections.newSetFromMap(
new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
static {
synchronized (loaderTypes) {
loaderTypes.add(ClassLoader.class); }
}
load class
// class ClassLoader
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// getClassLoadingLock(name):就是 并行化加载的作用所在
// 如果没有开启并行化,锁的就是当前classLoader本身
// 否则锁的范围大大减小,只锁 当前加载的这个class对象
synchronized (getClassLoadingLock(name)) {
// 1、判断当前class是否被加载过,如果被加载国,从缓存中返回此class
Class<?> c = findLoadedClass(name);
if (c == null) {
...
// 2、如果class没有被加载过,调用 父类加载器/虚拟机内置的加载器 加载class
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
...
// 3、如果 父类加载器/虚拟机内置的加载器 没有加载到class, 那么自己 再自己加载
if (c == null) {
'''
c = findClass(name);
...
}
}
// 4、根据选项链接这个class
// 真真正正的把这个普通的class对象 在jvm中真正“立起来”,
// 就是把符号引用替换为直接引用,
// 就是把这个静态的class文件转换为动态的运行时。
// a、检查:检查载入的class文件数据的正确性
// b、准备:给类的静态变量分配存储空间
// c、解析:将符号引用转化为直接使用
if (resolve) {
resolveClass(c);
}
return c;
}
}
// class ClassLoader
protected Object getClassLoadingLock(String className) {
Object lock = this;
// 实际上,classloader构造函数中 会初始化 并行加载涉及到的parallelLockMap容器
// 如果开启了并行加载,parallelLockMap = new ConcurrentHashMap<>();
// parallelLockMap 就不为null, 那么锁对象就变成了class了
// 否则锁对象就是 classloader本身
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
总结
classLaoder new阶段,只是缓存 此classLoader负责的那些class文件对象的URLs,只有 loadClass阶段才会各自加载到jvm中。