JavaClassLoader源码分析(下)
2019-12-27 本文已影响0人
spring_coderman
URLClassLoader.png
构造方法.png
构造方法解读.jpg
URL获取和设置.jpg
下面我把成员变量和静态代码块放在一块了
//URLClassLoader通过URLClassPath构建要扫描的类路径信息
//一个ucp包含一个URL对象的数组
/* The search path for classes and resources */
private final URLClassPath ucp;
/* The context to be used when loading classes and resources */
private final AccessControlContext acc;
/* A map (used as a set) to keep track of closeable local resources
* (either JarFiles or FileInputStreams). We don't care about
* Http resources since they don't need to be closed.
*
* If the resource is coming from a jar file
* we keep a (weak) reference to the JarFile object which can
* be closed if URLClassLoader.close() called. Due to jar file
* caching there will typically be only one JarFile object
* per underlying jar file.
*
* For file resources, which is probably a less common situation
* we have to keep a weak reference to each stream.
*/
private WeakHashMap<Closeable,Void>
closeables = new WeakHashMap<>();
static {
sun.misc.SharedSecrets.setJavaNetAccess (
new sun.misc.JavaNetAccess() {
public URLClassPath getURLClassPath (URLClassLoader u) {
return u.ucp;
}
public String getOriginalHostName(InetAddress ia) {
return ia.holder.getOriginalHostName();
}
}
);
//注册并行加载
ClassLoader.registerAsParallelCapable();
}
获取资源方法
/**
* Returns an input stream for reading the specified resource.
* If this loader is closed, then any resources opened by this method
* will be closed.
*
* <p> The search order is described in the documentation for {@link
* #getResource(String)}. </p>
*
* @param name
* The resource name
*
* @return An input stream for reading the resource, or {@code null}
* if the resource could not be found
*
* @since 1.7
*/
public InputStream getResourceAsStream(String name) {
//这里注意,这里是通过父类(ClassLoader)的方法加载URL资源的
URL url = getResource(name);
try {
if (url == null) {
return null;
}
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
if (urlc instanceof JarURLConnection) {
JarURLConnection juc = (JarURLConnection)urlc;
JarFile jar = juc.getJarFile();
//这个容器不是并发安全的,因此需要对其进行同步
synchronized (closeables) {
if (!closeables.containsKey(jar)) {
closeables.put(jar, null);
}
}
} else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
//这个容器不是并发安全的,因此需要对其进行同步
synchronized (closeables) {
closeables.put(is, null);
}
}
return is;
} catch (IOException e) {
return null;
}
}
想加载类,必须先找到类,以及所在的包/Jar文件地址,并解析
/**
* Finds and loads the class with the specified name from the URL search
* path. Any URLs referring to JAR files are loaded and opened as needed
* until the class is found.
*
* @param name the name of the class
* @return the resulting class
* @exception ClassNotFoundException if the class could not be found,
* or if the loader is closed.
* @exception NullPointerException if {@code name} is {@code null}.
*/
//上面的注释说明JAR文件也是一种URL资源,当需要加载类的时候就需要先加载JAR并打开这个JAR文件
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 {
//调用URLClaassLoader的私有方法
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;
}
找到之后就能读取字节码文件,并进行详细的类定义,定义完成之后,会被JVM放在元数据区或者永久代,包括字节码中的详细的属性信息,方法信息等
/*
* Defines a Class using the class bytes obtained from the specified
* Resource. The resulting Class must be resolved before it can be
* used.
*/
//这个方法就是具体加载类的实现,通过类的字节码文件进行解析和加载
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
if (i != -1) {
//这里是一个全路径名(如com.xx.xx.Person)
String pkgname = name.substring(0, i);
// Check if package already loaded.
//由于这个资源可能是个包,所以先进行包的定义
//先定义包的逻辑其实也是通过ClassLoader类中的方法定义的
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
//这里的代码应该跟之前的版本不太一样,因为使用了NIO
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
//JVM加载类性能的探针统计,
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
//这里最终会调用到ClassLoader类中的方法,进行具体的定义
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
//这里最终会调用到ClassLoader类中的方法,进行具体的定义
return defineClass(name, b, 0, b.length, cs);
}
}
URLClassLoader总结:
- URLClassLoader是JVM类加载机制的具体实现,一方面双亲委派模型中的ExtClassLoader/AppClassLoader这两个类加载器就是借助URLClassLoader实现的
- URLClassLoader对URL/URI资源类型的类做了一个加载的抽象
- URLClassLoader中find/defineClass底层其实还是调用父类ClassLoader方法实现的,
相关链接:
https://www.cnblogs.com/gdpuzxs/p/7044963.html
http://blog.itpub.net/31561269/viewspace-2222522/
https://www.cnblogs.com/editice/p/5420712.html
类加载机制的源码体系梳理完了,我们总体上学到了什么
- java类加载机制的JVM底层源码实现,关键方法解读
- java类加载机制中的双亲委派模型的原貌是啥
- java类加载机制中的双亲委派模型中有哪些类
- 自定义类加载器怎么实现,怎么用,然后满足自己的需求
- java类加载机制中的类加载过程
- java类加载机制在整个JVM类加载,使用,初始化流程中的过程。