首页 > 代码库 > JVM-类装载器

JVM-类装载器

一、class装载流程

技术分享

加载:
加载是装在类的第一个阶段,在此阶段是取得类的二进制流,转为方法区数据结构,在java堆中生成对应的java.lang.class对象

链接:
链接分为三个步骤:验证、准备、解析

1、验证:
目的:保证Class流的格式是正确的
文件格式的验证:是否以0xCAFEBABE开头;版本号是否合理….等等
元数据验证:是否有父类,若有父类验证父类class是否存在;继承了final类?若是final类则不可有子类…..等等
字节码验证:运行检查;栈数据类型和操作码数据参数吻合….等等
符号引用验证:常量池中描述类是否存在;访问的方法或字段是否存在且有足够的权限

2、准备:
分配内存,并为类设置初始值 (方法区中)
public static int v=1;
在准备阶段中,v会被设置为0,在初始化的<clinit>中才会被设置为1;
对于static final类型,在准备阶段就会被赋上正确的值
public static final int v=1; 此句在准备阶段v就会被设置为1;

3、解析:
符号引用替换为直接引用;
符号引用只是一种表示的方式,比如某个类继承java.lang.object,在符号引用阶段,只会记录该类是继承”java.lang.object”,以这种字符串的形式保存,但是不能保证该对象被记载。
直接引用就是真正能使用的引用,它是指针或者地址偏移量,引用对象一定在内存。最终知道在内存中到底放在哪里。
替换后,Class才能索引到它要用的那些内容

1、类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。

2、字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束,查找流程如下图所示:
技术分享

3、类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。

4、接口方法解析:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。

初始化:
执行类构造器<clinit>
static变量 、static{}语句被赋值
子类的<clinit>调用前保证父类的<clinit>被调用
<clinit>是线程安全的

二、类装载器ClassLoader

什么是类装载器ClassLoader?
1、ClassLoader是一个抽象类
2、ClassLoader的实例将读入Java字节码将类装载到JVM中
3、ClassLoader可以定制,满足不同的字节码流获取方式
4、ClassLoader负责类装载过程中的加载阶段

ClassLoader的重要方法
1、public Class

三、JDK中ClassLoader默认设计模式

分类
1、BootStrap ClassLoader (启动ClassLoader)
它负责将 /lib 下面的类库加载到内存中,若定义了路径-Xbootclasspath,也会加载定义路径下的jar或者class;
2、Extension ClassLoader (扩展ClassLoader)
它负责将 /lib/ext 下面的类库加载到内存中
3、App ClassLoader (应用ClassLoader/系统ClassLoader)
它负责将classpass下面的类库和类加载到内存中
4、Custom ClassLoader(自定义ClassLoader)
5、线程上下文类加载器

加载设计模式

技术分享

检查类是否已加载时顺序为:app classloader -> extension classlocader ->bootstrap classloader;

若类没有被加载,则加载顺序为:bootstrap classloader -> extension classlocader ->app classloader;

验证代码:
我们将生成的class代码放在一个任意的文件夹中和classpath中,两个class文件的名称和路径完全相同,在指定bootstrap class loader 的 -Xbootclasspath路径为放置class代码的路径,运行程序,查看时那个class被先加载的

package two;

public class HelloClassLoader {

    public void print(){
        System.out.println("I am in app loader");
    }
}

这段代码生成的class文件放在了classpath中,按上述,它应该被app classloader 加载

package two;

public class HelloClassLoader {

    public void print(){
        System.out.println("I am in bootstrap loader");
    }
}

这段代码生成的class文件,放置在任意目录F:/tmp/two下,但执行了JVM参数,如下图所示
技术分享

运行代码:

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        HelloClassLoader hl = new HelloClassLoader();
        hl.print();
    }

}

运行结果:
技术分享

由运行结果可知,先执行的是:BootStrap ClassLoader

类加载器间的关系

app class loader 的父级是 extension class loader
extension class loader 的父级是 bootstrap class loader

验证三者之间关系,代码如下:

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
    }

}

输出结果:
sun.misc.LauncherAppClassLoader@18b4aac2sun.misc.Launcher<script type="math/tex" id="MathJax-Element-8">AppClassLoader@18b4aac2 sun.misc.Launcher</script>ExtClassLoader@dcf3e99
null

我们首先看一下java.lang.ClassLoader抽象类中默认实现的构造函数:

    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

        /**
     * Creates a new class loader using the specified parent class loader for
     * delegation.
     *
     * <p> If there is a security manager, its {@link
     * SecurityManager#checkCreateClassLoader()
     * <tt>checkCreateClassLoader</tt>} method is invoked.  This may result in
     * a security exception.  </p>
     *
     * @param  parent
     *         The parent class loader
     *
     * @throws  SecurityException
     *          If a security manager exists and its
     *          <tt>checkCreateClassLoader</tt> method doesn‘t allow creation
     *          of a new class loader.
     *
     * @since  1.2
     */
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

    /**
     * Creates a new class loader using the <tt>ClassLoader</tt> returned by
     * the method {@link #getSystemClassLoader()
     * <tt>getSystemClassLoader()</tt>} as the parent class loader.
     *
     * <p> If there is a security manager, its {@link
     * SecurityManager#checkCreateClassLoader()
     * <tt>checkCreateClassLoader</tt>} method is invoked.  This may result in
     * a security exception.  </p>
     *
     * @throws  SecurityException
     *          If a security manager exists and its
     *          <tt>checkCreateClassLoader</tt> method doesn‘t allow creation
     *          of a new class loader.
     */
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

 我们再看一下ClassLoader抽象类中parent成员的声明:

private final ClassLoader parent;

声明为私有变量的同时并没有对外提供可供派生类访问的public或者protected设置器接口(对应的setter方法),结合前面的测试代码的输出,我们可以推断出:
  1. 系统类加载器(AppClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为标准扩展类加载器(ExtClassLoader)。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)
  2. 扩展类加载器(ExtClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为null。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)

类加载双亲委派机制介绍和分析

JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
loadClass源码如下:

    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;
        }
    }

当loadClass时,先去查找是否该Class已经被加载,若class没有被加载,则调用父级去加载class,若父级为null,则调用bootstrap classloader加载class。

参考博客:http://blog.csdn.net/zhoudaxia/article/details/35824249

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    JVM-类装载器