寻找类字节码文件并构造出类在JVM内部表示的组件.负责运行时查找和装入Class字节码文件
查找装载class字节码文件
执行校验,准备和解析步骤,其中解析步骤时可选的
检查装载Class文件的正确性
给类的静态变量分配存储空间
将符号引用转换为直接引用
对类的静态变量,方法,代码块执行初始化操作
最顶层的装载器,它不是ClassLoader的子类,采用C++编写,因此在JAVA中不可见。主要负责装载JRE核心类库。可以通过jvm启动参数-Xbootclasspath改变该加载器加载的路径
主要负责加载JRE扩展目录ext下的包,可以通过-D java.ext.dirs选项指定目录
负责加载当前工程目录下,classpath下的包或者class文件
其中Extention ClassLoader & AppClassLoader是ClassLoader的子类,根加载器是扩展加载器的父加载器,扩展加载器是应用加载器的父加载器。在默认情况下使用应用加载器
类加载采用“全盘负责委托机制”。
“全盘负责”:在类加载时指定一个ClassLoader,除非显示声明其他的加载器,否则该类所依赖的类以及引用的类都由该加载器加载。
“委托机制”:类加载时优先委托父加载器寻找并加载目标类,只有在父加载器没有找到的情况下,才从自己的classpath路径下查找并加载目标类。该点主要是出于安全的考虑,在classpath路径下定义JDK中已经存在的类,由于该机制,JDK中的类都由父加载器开始查找并加载.每个加载器都有缓存,在委托时优先查找缓存,如果缓存中存在,那么直接返回,否者才执行查找和加载
类文件被加载后,在JVM内部对应拥有一个java.lang.Class类描述对象,类的每个实例拥有类描述对象的引用,类描述对象拥有类加载器的引用
Bootstrap ClassLoader-->Extention ClassLoader-->AppClass Loader即Bootstrap ClassLoader最先启动,接着是Extenion ClassLoader,最后是AppClass Loader
1.定义Bootstrap ClassLoader加载路径(sun.boot.class.path)
2.创建Extention ClassCloader
3.创建App ClassLoader并设置App ClassLoader的父类:Extention ClassCloader
public class Launcher {
//Bootstrap ClassLoader加载路径
private static String bootClassPath = System.getProperty("sun.boot.class.path");
//该处定义的App ClassLoader
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
//创建Extention ClassLoader
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {......}
// Now create the class loader to use to launch the application
try {
//创建AppClassLoader并传入父加载器,由此可看AppClassLoader父加载器是Extention ClassLoader
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {......}
//设置AppClassLoader为当前线程上下问类加载器
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
}
public ClassLoader getClassLoader() {
return loader;
}
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
}
该类中定义了Bootstrap ClassLoader加载路径,同时创建了Extention ClassLoader以及AppClassLoader,如下是Bootstrap ClassLoader加载路径信息
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/resources.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/sunrsasign.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jsse.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jce.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jfr.jar
/usr/lib/jvm/java-8-openjdk-amd64/jre/classes
从输出路径上看Bootstrap ClassLoader主要加载的是jre/lib目录下的资源.即JVM Runtime核心库
1.根据查找路径获得路径(jav.ext.dirs)下文件
2.将文件路径转换为URL
3.调用父类构造函数,创建ExtClassLoader并设置父加载器
static class ExtClassLoader extends URLClassLoader {
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();//获得查找路径下的所有文件
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public ExtClassLoader run() throws IOException {
......
//创建Extention ClassLoader
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {......}
}
public ExtClassLoader(File[] dirs) throws IOException {
//调用父类URLClassLoader的构造函数并传入父加载器,此时为null
super(getExtURLs(dirs), null, factory);
}
private static File[] getExtDirs() {
//Extention ClassLoader加载路径
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 urls = new Vector();
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;
}
static URL getFileURL(File file) {
try {
file = file.getCanonicalFile();
} catch (IOException e) {}
try {
return ParseUtil.fileToEncodedURL(file);
} catch (MalformedURLException e) {
// Should never happen since we specify the protocol...
throw new InternalError(e);
}
}
如下是java.ext.dirs的输出信息
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext
/usr/java/packages/lib/ext
1.获得java.classs.path路径下的所有文件
2.解析文件路径为URL
3.调用父类构造函数创建AppClassLoader并设置父类
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
//AppClassLoader加载路径
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
}
}
}
private static URL[] pathToURLs(File[] path) {
URL[] urls = new URL[path.length];
for (int i = 0; i < path.length; i++) {
urls[i] = getFileURL(path[i]);
}
// DEBUG
//for (int i = 0; i < urls.length; i++) {
// System.out.println("urls[" + i + "] = " + '"' + urls[i] + '"');
//}
return urls;
}
Java.class.path输出信息
/work/new_workspace/aaa/bin
java.lang.Object
java.lang.ClassLoader
java.security.SecureClassLoader
java.net.URLClassLoader
ExtClassLoader
AppClassLoader
可以通过getParent()获得当前加载器的父加载器
//Main为自定义Class
System.out.println(Main.class.getClassLoader());
System.out.println(Main.class.getClassLoader().getParent());
System.out.println(Main.class.getClassLoader().getParent().getParent());
System.out.println(Boolean.class.getClassLoader());
sun.misc.Launcher$AppClassLoader@330bedb4
sun.misc.Launcher$ExtClassLoader@5cad8086
null
null
为什么获得ExtClassLoader的父加载器为null?这和每个加载器都有一个父加载器违背.这个属于正常现象.具体原因如下:
1.从ExtClassLoader构造函数super(getExtURLs(dirs), null, factory);可以看出此时传入的就是null
2.Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,不是一个JAVA类,无法在java代码中获取它的引用,凡是sun.boot.class.path路径下的包以及类都是由它加载。JVM初始化sun.misc.Launcher创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。BootstrapClassCloader没有父加载器,但是它却可以作为一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象.我们可以分析getParent()的源码
父加载器可以直接由外界指定,如果外界不指定,那么采用AppClassLoader作为父加载器
//在创建ExtClassLoader时,采用super(getExtURLs(dirs), null, factory).所以获得的是null
public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(parent);
}
public abstract class ClassLoader {
//父加载器直接由外部指定
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
}
//没有指定父加载器的情况下将系统加载器设置为父加载器即AppClassLoader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
...
}
public final ClassLoader getParent() {
if (parent == null)
return null;
..........
return parent;
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
......
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
//通过Launcher获取ClassLoader,其实就是AppClassLoader
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
scl = l.getClassLoader();
}
查找给定名称的资源。资源的名称是一个/分隔的路径名称标识资源,优先查找父加载器,如果找不到在从Bootstrap ClassLoader路径中查找并加载
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
查找给定名称的所有资源。资源的名称是一个/分隔的路径名称标识资源,优先查找父加载器,如果找不到在从Bootstrap ClassLoader路径中查找并加载,该模式下支持正则匹配
public Enumeration getResources(String name) throws IOException {
@SuppressWarnings("unchecked")
Enumeration[] tmp = (Enumeration[]) new Enumeration>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = getBootstrapResources(name);
}
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
采用AppClassLoader加载给定名称的资源
public static URL getSystemResource(String name) {
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResource(name);
}
return system.getResource(name);
}
采用AppClassLoader加载给定名称的所有资源
public static Enumeration getSystemResources(String name)
throws IOException
{
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResources(name);
}
return system.getResources(name);
}
查找具有给定名称的资源。类加载器实现应覆盖此方法以指定在哪里查找资源
protected URL findResource(String name) {
return null;
}
查找具有给定名称的所有资源。类加载器实现应覆盖此方法以指定在哪里查找资源
protected Enumeration findResources(String name) throws IOException {
return java.util.Collections.emptyEnumeration();
}
loadClass(String name) & loadClass(String name, boolean resolve)name指定了类装载器的名字,必须使用全限定名。resolve告诉装载器是否需要解析该类。在初始化之前,应该考虑进行类解析工作。并不是所有的类都需要解析的。如果JVM只需要知道该类是否存在或者找出该类的超类,那么就不需要解析该类
public abstract class ClassLoader {
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) {
try {
if (parent != null) {
//递归调用,查找父加载器
c = parent.loadClass(name, false);
} else {
//调用Bootstrap Classloader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {......}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
//调用findClass
c = findClass(name);
....
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
protected final Class> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class> findLoadedClass0(String name);
private Class> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
private native Class> findBootstrapClass(String name);
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
将类文件字节码数组装换成JVM内部的java.lang.Class对象,字节数组可以从本地系统,远程网络获取
protected final Class> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
protected final Class> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
private native Class> defineClass1(String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);
从本地文件系统载入Class文件,如果本地文件系统中不存在该Class文件,抛出ClassNotFoundError。该方法是JVM默认使用的装载器
protected final Class> findSystemClass(String name)
throws ClassNotFoundException
{
ClassLoader system = getSystemClassLoader();
if (system == null) {
if (!checkName(name))
throw new ClassNotFoundException(name);
Class> cls = findBootstrapClass(name);
if (cls == null) {
throw new ClassNotFoundException(name);
}
return cls;
}
return system.loadClass(name);
}
该方法查看ClassLoader是否已经加载了某个类,如果载入就返回Class对象,否则返回null,如果强行载入已经载入的类会抛出异常
protected final Class> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class> findLoadedClass0(String name);
1.编写一个类继承ClassLoader
2.重写findClass 方法
3.在findClass中调用defineClass即可
public class Thread implements Runnable {
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
}
contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()获得。
每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置
常见的用法是将Class文件按照某种加密手段进行加密,然后按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定了类,并且这个类只能被我们自定义的加载器进行加载,提高了程序的安全性。
http://blog.csdn.net/xyang81/article/details/7292380
http://blog.csdn.net/briblue/article/details/54973413#t36
JDK源码