算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解。要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机制和JVM字节码这样的核心特性。今天我将记录一下我在新的学习路程中对Java类加载机制的理解。
1.类加载机制
类加载是一个将类合并到正在运行着的JVM进程中的过程。首先要加载一个类,我们必须先得将类文件加载进来并连接,并且要加上大量的验证,随后会生成一个代表着这个类的class对象,然后就可以通过它去创建新的实例了。这就是我所理解的Java的类加载机制。
经过加载和连接后出来的class对象,说明这个类已经被加载到了JVM中,此后就不会再加载了。
2.类加载器和双亲委派模型
Java平台里有几个经典的类加载器,它们分别在平台的启动和常规操作中承担不同的任务:
根类加载器(Bootstrap ClassLoader)——通常在虚拟机启动不久后实例化,我们可以将其视作JVM的一部分,他的作用通常是负责加载系统的基础jar包(主要是rt.jar),并且它不做验证工作。我们开发者无法直接使用该加载器
扩展类加载器(Extension ClassLoader)——用来加载安装时自带的标准扩展。一般包括安全性扩展。我们开发者可以直接使用。
应用(或系统)类加载器(System ClassLoader)——这是应用最广泛的类加载器。它负责加载应用类。
定制类加载器(Custom ClassLoader)——在更复杂的环境中,我们开发者可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器以满足一些特殊的需求。比如说Spring框架里的封装了自己的类加载器(我会在随后的spring源码学习中遇到的吧)
线程上下文类加载器(Context ClassLoader)——默认为系统类加载器,可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,每个线程都可以将线程上下文类加载器预先设置为父线程的类加载器。这个主要用于打破双亲委派模型,容许父类加载器通过子类加载器加载所需要的类库。
说到双亲委派模型,我们可以通过这个图可知:
如果说一个类加载器收到类加载请求,它并不会马上去找,它会先把这个请求委托给他的父类加载器去完成,只有它的父类加载器反馈说找不到了,它才会自己去找(注:父类加载器它们的默认的目录路径都是不一样的,一个类在虚拟机里面是用它的全限定类名+它的类加载器来确立它的唯一性),采用双亲委派的好处可以保证系统中的类不混乱,如你自己写了一个java.lang.object类,并且路径也放在lib下面,此时编译后会有两个object类了,采用双亲委派就只会加载rt.jar里面的object而不加载你自己写的。
3.加载类
手动加载类有两种方式,Class.forName()和ClassLoader.loadClass()两种:
我们从源码来看看他们的区别
Class.forName()
它有两个重载方法
<pre name="code" class="java"> public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName0(className, true, ClassLoader.getCallerClassLoader());
}
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = ClassLoader.getCallerClassLoader();
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader);
}
/** Called after security checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException;
第一个方法默认初始化类。
第二个方法可以选择是否初始化类和可以选择类加载器。
它们俩最终都是返回forName0,而forName0有native关键字,原生态的方法,是其他语言实现的,我就不深究下去了。
ClassLoader.loadClass()
它同样也有两个重载方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
第一个方法的具体实现是第二个方法。resolve参数为false。
第二个方法先检查这个类是否被加载过,如果没有被加载过,那就通过双亲委派的模式去找,如果父类加载器不是根类加载器,那就递归调用,如果是,那就试着返回根类加载器所加载的类或者返回null,当虚拟机想要装入的不仅包括指定的类似,resolve参数为true,装入该类英语的所有其他类。这个方法还用了其他的类,它们基本都是用native修饰的,在这也不深究,知道是用来干嘛的就好了。
以上就是我所学习的关于Java类加载器的内容。
https://blog.csdn.net/donggua3694857/article/details/51932630