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[email protected]

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

时间: 2024-10-11 00:14:49

JVM-类装载器的相关文章

Java 编程的动态性, 第4部分: 用 Javassist 进行类转换--转载

讲过了 Java 类格式和利用反射进行的运行时访问后,本系列到了进入更高级主题的时候了.本月我将开始本系列的第二部分,在这里 Java 类信息只不过是由应用程序操纵的另一种形式的数据结构而已.我将这个主题的整个内容称为 classworking. 我将以 Javassist 字节码操作库作为对 classworking 的讨论的开始.Javassist 不仅是一个处理字节码的库,而且更因为它的另一项功能使得它成为试验 classworking 的很好的起点.这一项功能就是:可以用 Javassi

java之jvm学习笔记五(实践写自己的类装载器)

java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类装载器和安全管理器是可以被动态扩展的,或者说,他们是可以由用户自己定制的,今天我们就是动手试试,怎么做这部分的实践,当然,在阅读本篇之前,至少要阅读过笔记三. 下面我们先来动态扩展一个类装载器,当然这只是一个比较小的demo,旨在让大家有个比较形象的概念. 第一步,首先定义自己的类装载器,从Clas

java jvm学习笔记五(实践自己写的类装载器)

 欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类装载器和安全管理器是可以被动态扩展的,或者说,他们是可以由用户自己定制的,今天我们就是动手试试,怎么做这部分的实践,当然,在阅读本篇之前,至少要阅读过笔记三. 下面我们先来动态扩展一个类装载器,当然这只是一个比较小的demo,旨在让大家有个比较形象的概念. 第一步,首先

java之jvm学习笔记二(类装载器的体系结构)

java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新的解析一次, 第二种,即时解析,也就是转载到内存的字节码会被解析成本地机器码,并缓存起来以提高重用性,但是比较耗内存, 第三种,自适应优化解析,即将java将使用最贫乏的代码编译成本地机器码,而使用不贫乏的则保持字节码不变,一个自适应的优化器可以使得java虚拟机在80%-90%的时间里执行优化过的

深入探究jvm之类装载器

一.class装载验证流程 1.加载 1).取得类的二进制流. 2).转为方法区数据结构. 3).在Java堆中生成对应的java.lang.Class对象. 2.链接--验证(目的:保证Class流的格式是正确的) 1).文件格式的验证:是否是0xCAFEBASE开头.版本号是否正确等. 2).元数据验证:是否有父类.是否继承了final类.非抽象类是否实现了所有的抽象方法等. 3).字节码验证(最复杂):运行检查.栈数据类型和操作码数据参数是否吻合.跳转指令是否指定到合理的位置. 4).符号

JVM内存模型-转载

http://my.oschina.net/u/567296/blog/303780 JVM的内部结构如下图: JVM主要包括两个子系统和两个组件: 1. 两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统: 1.1 Class loader子系统的作用:根据给定的全限定名类名(如 java.lang.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域).Java程序员可以exten

JVM启动流程

JVM是Java程序运行的环境,同时是一个操作系统的一个应用程序进程,因此它有自己的生命周期,也有自己的代码和数据空间. JVM体系主要分为三个子系统和两大组件,分别是:类装载器子系统.执行引擎子系统和GC子系统,组件是内存运行数据区域和本地接口. JVM工作是指操作系统装入JVM,是通过JDK中的java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM获得JNIEnv接口 4.找到main()方法并运行 过程如下图: 一.J

JVM加载class文件的原理

当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件.那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做[加载 ].加载完成之后,我们就可以进行一系列的运行前准备工作了,比如: 为类静态变量开辟空间,将常量池存放在方法区内存中并实现常量池地址解析,初始化类静态变量等等.这篇文章我们要好好谈谈JVM是如何加载class文件的? 1.JVM加载类的过程       当我们使用命令来执行某一个Java程序(比如Test.class)的时候:java T

java Jvm工作原理学习笔记

一.         JVM的生命周期 1.       JVM实例对应了一个独立运行的java程序它是进程级别 a)     启动.启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点 b)     运行.main()作为该程序初始线程的起点,任何其他线程均由该线程启动.JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己

【Java高级】JVM内存区域模型和加载过程

JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中.