一看你就懂,超详细java中的ClassLoader详解
Posted frank909
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一看你就懂,超详细java中的ClassLoader详解相关的知识,希望对你有一定的参考价值。
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制。
备注:本文篇幅比较长,但内容简单,大家不要恐慌,安静地耐心翻阅就是
Class文件的认识
我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。如我们编写一个简单的程序HelloWorld.java
public class HelloWorld
public static void main(String[] args)
System.out.println("Hello world!");
如图:
然后,我们需要在命令行中进行java文件的编译
javac HelloWorld.java
可以看到目录下生成了.class文件
我们再从命令行中执行命令:
java HelloWorld
上面是基本代码示例,是所有入门JAVA语言时都学过的东西,这里重新拿出来是想让大家将焦点回到class文件上,class文件是字节码格式文件,java虚拟机并不能直接识别我们平常编写的.java源文件,所以需要javac这个命令转换成.class文件。另外,如果用C或者PYTHON编写的程序正确转换成.class文件后,java虚拟机也是可以识别运行的。更多信息大家可以参考这篇。
了解了.class文件后,我们再来思考下,我们平常在Eclipse中编写的java程序是如何运行的,也就是我们自己编写的各种类是如何被加载到jvm(java虚拟机)中去的。
你还记得java环境变量吗?
初学java的时候,最害怕的就是下载JDK后要配置环境变量了,关键是当时不理解,所以战战兢兢地照着书籍上或者是网络上的介绍进行操作。然后下次再弄的时候,又忘记了而且是必忘。当时,心里的想法很气愤的,想着是–这东西一点也不人性化,为什么非要自己配置环境变量呢?太不照顾菜鸟和新手了,很多菜鸟就是因为卡在环境变量的配置上,遭受了太多的挫败感。
因为我是在Windows下编程的,所以只讲Window平台上的环境变量,主要有3个:JAVA_HOME、PATH、CLASSPATH。
JAVA_HOME
指的是你JDK安装的位置,一般默认安装在C盘,如
C:\\Program Files\\Java\\jdk1.8.0_91
PATH
将程序路径包含在PATH当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javac
和java
两个命令。
一般的
PATH=%JAVA_HOME%\\bin;%JAVA_HOME%\\jre\\bin;%PATH%;
也就是在原来的PATH路径上添加JDK目录下的bin目录和jre目录的bin.
CLASSPATH
CLASSPATH=.;%JAVA_HOME%\\lib;%JAVA_HOME%\\lib\\tools.jar
一看就是指向jar包路径。
需要注意的是前面的.;
,.
代表当前目录。
环境变量的设置与查看
设置可以右击我的电脑,然后点击属性,再点击高级,然后点击环境变量,具体不明白的自行查阅文档。
查看的话可以打开命令行窗口
echo %JAVA_HOME%
echo %PATH%
echo %CLASSPATH%
好了,扯远了,知道了环境变量,特别是CLASSPATH时,我们进入今天的主题Classloader.
JAVA类加载流程
Java语言系统自带有三个类加载器:
- Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如
java -Xbootclasspath/a:path
被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。 - Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件。还可以加载
-D java.ext.dirs
选项指定的目录。 - Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
我们上面简单介绍了3个ClassLoader。说明了它们加载的路径。并且还提到了-Xbootclasspath
和-D java.ext.dirs
这两个虚拟机参数选项。
加载顺序?
我们看到了系统的3个类加载器,但我们可能不知道具体哪个先行呢?
我可以先告诉你答案
- Bootstrap CLassloder
- Extention ClassLoader
- AppClassLoader
为了更好的理解,我们可以查看源码。
看sun.misc.Launcher,它是一个java虚拟机的入口应用。
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;
try
extcl = ExtClassLoader.getExtClassLoader();
catch (IOException e)
throw new InternalError(
"Could not create extension class loader", e);
// Now create the class loader to use to launch the application
try
loader = AppClassLoader.getAppClassLoader(extcl);
catch (IOException e)
throw new InternalError(
"Could not create application class loader", e);
//设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
Thread.currentThread().setContextClassLoader(loader);
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader()
return loader;
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader
源码有精简,我们可以得到相关的信息。
- Launcher初始化了ExtClassLoader和AppClassLoader。
- Launcher中并没有看见BootstrapClassLoader,但通过
System.getProperty("sun.boot.class.path")
得到了字符串bootClassPath
,这个应该就是BootstrapClassLoader加载的jar包路径。
我们可以先代码测试一下sun.boot.class.path
是什么内容。
System.out.println(System.getProperty("sun.boot.class.path"));
得到的结果是:
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\resources.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\rt.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\sunrsasign.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\jsse.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\jce.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\charsets.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\jfr.jar;
C:\\Program Files\\Java\\jre1.8.0_91\\classes
可以看到,这些全是JRE目录下的jar包或者是class文件。
ExtClassLoader源码
如果你有足够的好奇心,你应该会对它的源码感兴趣
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader
static
ClassLoader.registerAsParallelCapable();
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
final File[] dirs = getExtDirs();
try
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>()
public ExtClassLoader run() throws IOException
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();
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;
......
我们先前的内容有说过,可以指定-D java.ext.dirs
参数来添加和改变ExtClassLoader的加载路径。这里我们通过可以编写测试代码。
System.out.println(System.getProperty("java.ext.dirs"));
结果如下:
C:\\Program Files\\Java\\jre1.8.0_91\\lib\\ext;C:\\Windows\\Sun\\Java\\lib\\ext
AppClassLoader源码
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
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);
return new AppClassLoader(urls, extcl);
);
......
可以看到AppClassLoader加载的就是java.class.path
下的路径。我们同样打印它的值。
System.out.println(System.getProperty("java.class.path"));
结果:
D:\\workspace\\ClassLoaderDemo\\bin
这个路径其实就是当前java工程目录bin,里面存放的是编译生成的class文件。
好了,自此我们已经知道了BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是查阅相应的环境属性sun.boot.class.path
、java.ext.dirs
和java.class.path
来加载资源文件的。
接下来我们探讨它们的加载顺序,我们先用Eclipse建立一个java工程。
然后创建一个Test.java
文件。
public class Test
然后,编写一个ClassLoaderTest.java文件。
public class ClassLoaderTest
public static void main(String[] args)
// TODO Auto-generated method stub
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
我们获取到了Test.class文件的类加载器,然后打印出来。结果是:
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
也就是说明Test.class文件是由AppClassLoader加载的。
这个Test类是我们自己编写的,那么int.class或者是String.class的加载是由谁完成的呢?
我们可以在代码中尝试
public class ClassLoaderTest
public static void main(String[] args)
// TODO Auto-generated method stub
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
cl = int.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
运行一下,却报错了
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" java.lang.NullPointerException
at ClassLoaderTest.main(ClassLoaderTest.java:15)
提示的是空指针,意思是int.class这类基础类没有类加载器加载?
当然不是!
int.class是由Bootstrap ClassLoader加载的。要想弄明白这些,我们首先得知道一个前提。
每个类加载器都有一个父加载器
每个类加载器都有一个父加载器,比如加载Test.class是由AppClassLoader完成,那么AppClassLoader也有一个父加载器,怎么样获取呢?很简单,通过getParent方法。比如代码可以这样编写:
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\\'s parent is:"+cl.getParent().toString());
运行结果如下:
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
这个说明,AppClassLoader的父加载器是ExtClassLoader。那么ExtClassLoader的父加载器又是谁呢?
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\\'s parent is:"+cl.getParent().toString());
System.out.println("ClassLoader\\'s grand father is:"+cl.getParent().getParent().toString());
运行如果:
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
java.lang.NullPointerException
at ClassLoaderTest.main(ClassLoaderTest.java:13)
又是一个空指针异常,这表明ExtClassLoader也没有父加载器。那么,为什么标题又是每一个加载器都有一个父加载器呢?这不矛盾吗?为了解释这一点,我们还需要看下面的一个基础前提。
父加载器不是父类
我们先前已经粘贴了ExtClassLoader和AppClassLoader的代码。
static class ExtClassLoader extends URLClassLoader
static class AppClassLoader extends URLClassLoader
可以看见ExtClassLoader和AppClassLoader同样继承自URLClassLoader,但上面一小节代码中,为什么调用AppClassLoader的getParent()
代码会得到ExtClassLoader的实例呢?先从URLClassLoader说起,这个类又是什么?
先上一张类的继承关系图
URLClassLoader的源码中并没有找到getParent()
方法。这个方法在ClassLoader.java中。
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;
// The class loader for the system
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;
private ClassLoader(Void unused, ClassLoader parent)
this.parent = parent;
...
protected ClassLoader(ClassLoader parent)
this(checkCreateClassLoader(), parent);
protected ClassLoader()
this(checkCreateClassLoader(), getSystemClassLoader());
public final ClassLoader getParent()
if (parent == null)
return null;
return parent;
public static ClassLoader getSystemClassLoader()
initSystemClassLoader();
if (scl == null)
return null;
return scl;
private static synchronized void initSystemClassLoader()
if (!sclSet)
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null)
Throwable oops = null;
//通过Launcher获取ClassLoader
scl = l.getClassLoader();
try
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
catch (PrivilegedActionException pae)
oops = pae.getCause();
if (oops instanceof InvocationTargetException)
oops = oops.getCause();
if (oops != null)
if (oops instanceof Error)
throw (Error) oops;
else
// wrap the exception
throw new Error(oops);
sclSet = true;
我们可以看到getParent()
实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:
- 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。
- 由
getSystemClassLoader()
方法生成,也就是在sun.misc.Laucher通过getClassLoader()
获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
我们主要研究的是ExtClassLoader与AppClassLoader的parent的来源,正好它们与Launcher类有关,我们上面已经粘贴过Launcher的部分代码。
public class Launcher
private static URLStreamHandlerFactory factory = new Factory();
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;
try
extcl = ExtClassLoader.getExtClassLoader();
catch (IOException e)
throw new InternalError(
"Could not create extension class loader", e);
// Now create the class loader to use to launch the application
try
//将ExtClassLoader对象实例传递进去
loader = AppClassLoader.getAppClassLoader(extcl);
catch (IOException e)
throw new InternalError(
"Could not create application class loader", e);
public ClassLoader getClassLoader()
return loader;
static class ExtClassLoader extends URLClassLoader
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
final File[] dirs = getExtDirs();
try
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>()
public ExtClassLoader run() throws IOException
//ExtClassLoader在这里创建
return new ExtClassLoader(dirs);
);
catch (java.security.PrivilegedActionException e)
throw (IOException) e.getException();
/*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException
super(getExtURLs(dirs), null, factory);
我们需要注意的是
ClassLoader extcl;
extcl <以上是关于一看你就懂,超详细java中的ClassLoader详解的主要内容,如果未能解决你的问题,请参考以下文章
一看你就懂,超详细 java 中的 ClassLoader 详解
根据jdk1.8源码整理而得,java集合体系(继承实现关系)图解,超清晰,一看就懂,方便记忆