类加载是怎么样的?从哪里开始加载,如何加载
前提知识
- 虚拟机自带的加载器:
启动类加载器(Bootstrap)C++
扩展类加载器(Extension)Java
应用程序类加载器(AppClassLoader)
Java也叫系统类加载器,加载当前应用的classpath的所有类
- AppClassLoader 和 Extension 都是Launcher的静态类
(1)扩展类加载器 运行 ${JAVA_HOME}/jre/lib/ext 下面的 jar 包
ExtensionClassLoader,这个类加载器其实也是类似的,就是你的Java安装目录下,有一个“lib\ext”目录。这里面有一些类,就是需要使用这个类加载器来加载的,支撑你的系统的运行。是从Launcher 类里面获取到的
关键属性:类文件地址是从java.ext.dirs 获取的
String s = System.getProperty("java.ext.dirs");
//打印得出来的地址是
/Users/hilbert/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:
/usr/lib/java
打开 ${JAVA_HOME}/jre/lib/ext 地址发现:里面存的是jar包嘛,我们的项目maven引用其实也是jar 包的形式嘛
解压一个jar 包的效果:存的也只有包路径,不会有文件路径的。(com.wei.xxxx)
ExtClassLoader 具体类的方法
static class ExtClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
private static volatile ExtClassLoader instance = null;
//双重判断加载实例嘛
public static ExtClassLoader getExtClassLoader() throws IOException
{
if (instance == null) {
synchronized(ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
private static ExtClassLoader createExtClassLoader() throws IOException {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
//获取类文件地址
final File[] dirs = getExtDirs();
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();
}
}
public ExtClassLoader(File[] dirs) throws IOException {
//设置parent classLoader
super(getExtURLs(dirs), null, factory);
SharedSecrets.getJavaNetAccess().
getURLClassPath(this).initLookupCache(this);
}
//获取java.jar文件的地址
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;
}
private static URL[] getExtURLs(File[] dirs) throws IOException {
Vector<URL> urls = new Vector<URL>();
for (int i = 0; i < dirs.length; i++) {
String[] files = dirs[i].list();
if (files != null) {
for (int j = 0; j < files.length; j++) {
if (!files[j].equals("meta-index")) {
File f = new File(dirs[i], files[j]);
urls.add(getFileURL(f));
}
}
}
}
URL[] ua = new URL[urls.size()];
urls.copyInto(ua);
return ua;
}
}
(2)应用程序类加载器 (加载用户自定义 -classpath 或者 Jar 包的 Class-Path 定义的第三方包)
ApplicationClassLoader,这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类。其实你大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里。这些地址是URLClassLoader 的URLClassPath ucp 属性
String s = System.getProperty("java.class.path"); 获取都是jar 包的地址
//打印出来的地址 第三方的包
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:
/Users/hilbert/.m2/repository/org/mybatis/mybatis/3.5.1/mybatis-3.5.1.jar:
/Users/hilbert/.m2/repository/org/openjdk/jol/jol-core/0.9/jol-core-0.9.jar
AppClassLoader 具体类的方法
static class AppClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
//jar 包所在地址
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
//extcl 是ExtClassLoader
return new AppClassLoader(urls, extcl);
}
});
}
final URLClassPath ucp;
AppClassLoader(URL[] urls, ClassLoader parent) {
//设置ExtClassLoader 为parent ClassLoader
super(urls, parent, factory);
ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
ucp.initLookupCache(this);
}
//AppClassLoader 加载类的方法
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
//查看这个包地址在不在锁管理的jar范围内
if (ucp.knownToNotExist(name)) {
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
return (super.loadClass(name, resolve));
}
}
(3)启动类加载器 负责加载 ${JAVA_HOME}/jre/lib 部分 jar 包
BootstrapClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的。所以一旦你的JVM启动,那么首先就会依托启动类加载器,去加载你的Java安装目录下的“lib”目录中的核心类库。
Launcher 里面有一个核心方法
private static String bootClassPath =System.getProperty("sun.boot.class.path");
//查出来的是:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes
对外提供了查询BootStrap管理jar 的地址方法 也是跟URLClassLoader的udp 属性差不多
public static URLClassPath getBootstrapClassPath() {
return BootClassPathHolder.bcp;
}
private static class BootClassPathHolder {
//bcp 指向的地址是 System.getProperty("sun.boot.class.path")
static final URLClassPath bcp;
static {
URL[] urls;
if (bootClassPath != null) {
urls = AccessController.doPrivileged(
new PrivilegedAction<URL[]>() {
public URL[] run() {
File[] classPath = getClassPath(bootClassPath);
int len = classPath.length;
Set<File> seenDirs = new HashSet<File>();
for (int i = 0; i < len; i++) {
File curEntry = classPath[i];
if (!curEntry.isDirectory()) {
curEntry = curEntry.getParentFile();
}
if (curEntry != null && seenDirs.add(curEntry)) {
MetaIndex.registerDirectory(curEntry);
}
}
return pathToURLs(classPath);
}
}
);
} else {
urls = new URL[0];
}
bcp = new URLClassPath(urls, factory, null);
bcp.initLookupCache(null);
}
}
简单知道我们平常的类加载器是什么
public class ClassLoaderA {
private Integer a = new Integer(100);
public void add() {
a++;
}
public Integer getA() {
return a;
}
public void setA(Integer a) {
this.a = a;
}
}
public void test() {
ClassLoaderA classLoaderA = new ClassLoaderA();
System.out.println(classLoaderA.getClass().getClassLoader().getParent().getParent());
System.out.println(classLoaderA.getClass().getClassLoader().getParent());
System.out.println(classLoaderA.getClass().getClassLoader());
}
//运行结果。说明ExtClassLoader 没有父类,那代码怎么运行到的?
null
sun.misc.Launcher$ExtClassLoader@4fca772d
sun.misc.Launcher$AppClassLoader@18b4aac2
类加载过程:其实将一个class 文件load 到内存中
public static void main(String[] args) throws Exception {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
//类的包路径
Class<?> a = classLoader.loadClass("锁.ClassLoader.ClassLoaderA");
ClassLoaderA instance = (ClassLoaderA) a.newInstance();
System.out.println(instance.getA());
}
//执行结果
100
(1)ClassLoader classLoader = ClassLoader.getSystemClassLoader();
//SystemClassLoader其实就是Launcher 的AppClassLoader
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
//从launcher获取AppClassLoader
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
//java 沙箱机制(下一篇文章会讲到)
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
。。。。
}
sclSet = true;
}
}
(2) Class<?> a = classLoader.loadClass(“锁.ClassLoader.ClassLoaderA”)
- 先调用的是appClassLoader的loadClass 方法
- 判断这个包路径在不在自己管理的范围内
- 判断是否已经加载过了。就不需要加载了
- 调用父类的加载方法(并不是parentClassLoader:ExtClassLoader的加载方法)
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
//1.判断这个包路径在不在自己管理的范围内
if (ucp.knownToNotExist(name)) {
//2.判断是否已经加载过了。就不需要加载了
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
//3.调用父类的加载方法(并不是parentClassLoader:ExtClassLoader的加载方法)
return (super.loadClass(name, resolve));
}
(3) super.loadClass(name, resolve)
- 加一个正在加载对象的锁
- 判断一下是否加载过了
- 如果父加载器不为空的时候,使用父加载器去加载
- 如果父类为空的时候,使用bootStrap加载器
- 如果上一层取出来为空的话就用了自己的加载类的方法 ,使用URLClassLoader方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
//1.加一个正在加载对象的锁
synchronized (getClassLoadingLock(name)) {
// 2.判断一下是否加载过了。
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//3如果父加载器不为空的时候,使用父加载器去加载
c = parent.loadClass(name, false);
} else {
//4如果父类为空的时候,使用bootStrap加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
//如果上一层取出来为空的话就用了自己的加载类的方法
if (c == null) {
long t1 = System.nanoTime();
//使用自己的类加载方法,使用URLClassLoader 方法
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
//正在生成这个对象的锁,防止重复加载
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
URLClassLoader findClass 方法
protected Class<?> findClass(final String name) throws ClassNotFoundException{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//查找对应的class文件对象 锁/ClassLoader/ClassLoaderA.class
String path = name.replace('.', '/').concat(".class");
//从ucp获取资源地址文件
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//获取class文件
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;
}
类加载器流程
(1)双亲委派机制的好处,为什么要从BootStrapClassLoader 开始加载,而不是从AppClassLoader 开始加载有什么问题?为什么开头是AppClassLoader 但是要往上找,而不是先从自身开始找。
BootStrapClassLoader 和AppClassLoader 加载的jar 包不一样,BootStrap
主要是加载的是核心类,支持Java程序的启动,所以需要最先加载,但是不能放在最前面,因为后面加载不到了,加载的都是用户自定义的包,所以不能放在最前面。要让AppClassLoader放在最前面,高频使用。提高效率。(有缓存,可以防止重复加载)
(2)我用一个类加器就可以了?为什么要那么多类加载器。比如我AppClassLoader 的jar查询地址,放所有的文件的jar不可以吗?会有什么问题?(或者是分层次加载有什么好处?)
Jar 的加载顺序是怎么样的 ?关键代码如下:
//查找对应的class文件对象 锁/ClassLoader/ClassLoaderA.class
String path =name.replace(’.’, ‘/’).concat(".class");
//从ucp获取资源地址文件
Resource res =ucp.getResource(path, false);
//获取Class文件
return defineClass(name,res);
重点是ucp.getResource方法 已经决定了用哪个class 文件
- 每个ClassLoader 下面会有一些jar 文件,每个jar 文件就是一个JarLoader
public Resource getResource(String name, boolean check) {
if (DEBUG) {
System.err.println("URLClassPath.getResource(\"" + name + "\")");
}
Loader loader;
//获取缓存里面符号这个class文件的jar包位置下标
int[] cache = getLookupCache(name);
for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) {
//从jar里面解析获取对应的class文件。谁的下标越早就越容易找到
Resource res = loader.getResource(name, check);
if (res != null) {
return res;
}
}
return null;
}
private synchronized int[] getLookupCache(String name) {
//lookupCacheURLs包含已经加载并缓存的URL路径
//lookupCacheEnabled参数表示是否启用查找缓存,通过属性sun.cds.enableSharedLookupCache指定,默认为true
if (lookupCacheURLs == null || !lookupCacheEnabled) {
return null;
}
//lookupCacheLoader在initLookupCache方法中完成初始化,该方法在URLClassPath实例化完成调用,通常是URLClassLoader的父类加载器
//getLookupCacheForClassLoader是一个本地方法,该方法返回包含指定name的资源的路径在URL[]中的索引
//比如URL[]为{a.jar, b.jar, c.jar, d.jar},a.jar和c.jar都包含有foo/Bar.class,则该方法返回[0,2]
int[] cache = getLookupCacheForClassLoader(lookupCacheLoader, name);
if (cache != null && cache.length > 0) {
int maxindex = cache[cache.length - 1]; // cache[] is strictly ascending.
//确保对应路径的Loader对象已实例化
if (!ensureLoaderOpened(maxindex)) {
if (DEBUG_LOOKUP_CACHE) {
System.out.println("Expanded loaders FAILED " +
loaders.size() + " for maxindex=" + maxindex);
}
return null;
}
}
return cache;
}
那么class 类的加载顺序是跟jar 包的顺序有关系的(如果jar 里面还依赖其他jar包怎么办)
private synchronized Loader getNextLoader(int[] cache, int index) {
if (closed) {
return null;
}
if (cache != null) {
if (index < cache.length) {
Loader loader = loaders.get(cache[index]);
if (DEBUG_LOOKUP_CACHE) {
System.out.println("HASCACHE: Loading from : " + cache[index]
+ " = " + loader.getBaseURL());
}
return loader;
} else {
return null; // finished iterating over cache[]
}
} else {
return getLoader(index);
}
}
//重点getLoader 方法 有一个栈在生效
private synchronized Loader getLoader(int index) {
if (closed) {
return null;
}
while (loaders.size() < index + 1) {
URL url;
synchronized (urls) {//urls 是我们jar 地址
if (urls.empty()) {
return null;
} else {
url = urls.pop();
}
}
String urlNoFragString = URLUtil.urlNoFragString(url);
if (lmap.containsKey(urlNoFragString)) {
continue;
}
Loader loader;
try {
loader = getLoader(url);//生成这个jar 对应JarLoader
URL[] urls = loader.getClassPath();//找到这个jar依赖的其他jar
if (urls != null) {
//把这些依赖的jar 加入到这个栈
push(urls);
}
} catch (IOException e) {
continue;
} catch (SecurityException se) {
if (DEBUG) {
System.err.println("Failed to access " + url + ", " + se );
}
continue;
}
validateLookupCache(loaders.size(), urlNoFragString);
loaders.add(loader);
lmap.put(urlNoFragString, loader);
}
if (DEBUG_LOOKUP_CACHE) {
System.out.println("NOCACHE: Loading from : " + index );
}
return loaders.get(index);
}
利用一个栈栈和一个队列 完成了class文件的加载顺序
- 加载顺序:
- 变成 :
- 最终:
appClassLoader的顺序是:部分核心库->项目自身->项目第三方依赖,可以从System.getProperty(“java.class.path”) 查看
(1)这时候出现同包名java.lang.String(自定义的类跟系统的类出现冲突怎么办,怎么加载)?
//自己项目定义的
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("test");
}
}
产生报错:因为他优先从bootStrap先找到了,所以不会加载
这是双亲委派的好处之一:
(1)防止一些第三方提供的jar包里面有恶意的class文件(例如java.lang.String)把你的程序破坏掉。