您的位置:首页 > 其它

JVM类加载机制

2017-06-22 16:07 141 查看
类加载模型:

Java程序运行的场所是内存,当在命令行下执行:

java HelloWorld

命令的时候,JVM会将HelloWorld.class加载到内存中,并形成一个Class的对象HelloWorld.class。

其中的过程就是类加载过程:

1、寻找jre目录,寻找jvm.dll,并初始化JVM;

2、产生一个Bootstrap Loader(引导类加载器);

3、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。

4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。

5、最后由AppClass Loader加载HelloWorld类。

类加载过程:

类加载分为三个步骤:加载,连接,初始化;

加载

根据一个类的全限定名(如cn.edu.hdu.test.HelloWorld.class)来读取此类的二进制字节流到JVM内部;将字节流所代表的静态存储结构转换为方法区的运行时数据结构(hotspot选择将Class对象存储在方法区中,Java虚拟机规范并没有明确要求一定要存储在方法区或堆区中),转换为一个与目标类型对应的java.lang.Class对象;

连接

验证

验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证;

准备

为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量将不再此操作范围内);

解析

将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。这个阶段可以在初始化之后再执行。

初始化

在连接的准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员自己写的逻辑去初始化类变量和其他资源,举个例子如下:

    public static int value1  = 5;

    public static int value2  = 6;

    static{

        value2 = 66;

    }

在准备阶段value1和value2都等于0;

在初始化阶段value1和value2分别等于5和66;

所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是<clinit>方法,即类/接口初始化方法,该方法只能在类加载的过程中由JVM调用;编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量;如果超类还没有被初始化,那么优先对超类初始化,但在<clinit>方法内部不会显示调用超类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的超类<clinit>方法已经被执行。JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。(所以可以利用静态内部类实现线程安全的单例模式)

如果一个类没有声明任何的类变量,也没有静态代码块,那么可以没有类<clinit>方法;

何时触发初始化:

1、 为一个类型创建一个新的对象实例时(比如new、反射、序列化)

2、 调用一个类型的静态方法时(即在字节码中执行invokestatic指令)

3、 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式

4、 调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)

5、 初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)

6、 JVM启动包含main方法的启动类时。

各种类加载器解释:

1、Bootstrap Loader(引导类加载器):加载System.getProperty("sun.boot.class.path")所指定的路径或jar。

2、Extended Loader(标准扩展类加载器ExtClassLoader):加载System.getProperty("java.ext.dirs")所指定的路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java-Djava.ext.dirs=d:\projects\testproj\classes HelloWorld

3、AppClass Loader(系统类加载器AppClassLoader):加载System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classesHelloWorld

ExtClassLoader和AppClassLoader在JVM启动后,会在JVM中保存一份,并且在程序运行中无法改变其搜索路径。如果想在运行时从其他搜索路径加载类,就要产生新的类加载器。

类加载特点:

1、运行一个程序时,总是由AppClass Loader(系统类加载器)开始加载指定的类。

2、在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。(双亲委派机制)

3、Bootstrap Loader(引导类加载器)是最顶级的类加载器了,其父加载器为null.

类加载获取:

public
class
test2 {  
        public
static void
main(String[] args) {  

                test2 hello = new test2();  

                Class c =hello.getClass();  
                ClassLoader loader =c.getClassLoader();  
               System.out.println(loader);  
               System.out.println(loader.getParent());  
                System.out.println(loader.getParent().getParent());  

        }  


输出:

sun.misc.Launcher$AppClassLoader@6150818a
sun.misc.Launcher$ExtClassLoader@6c68bcef
null
从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(引导类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null。

类加载有三种方式:

1、启动应用时候由JVM初始化加载

2、通过Class.forName()方法动态加载

3、通过ClassLoader.loadClass()方法动态加载

public
class
HelloWorld {  
        public
static void
main(String[] args)
throws ClassNotFoundException {  

                ClassLoader loader = HelloWorld.class.getClassLoader();  

                System.out.println(loader);  

                //使用ClassLoader.loadClass()来加载类,不会执行初始化块  

//               loader.loadClass("Test2");  
                //使用Class.forName()来加载类,默认会执行初始化块  

//               Class.forName("Test2");  
                //使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块  

                Class.forName("Test2",false,loader);  

        }  
}

public
class
Test2 {  
        static {  

                System.out.println("静态初始化块执行了!");  

        }} 
分别切换加载方式,会有不同的输出结果。
Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
注:
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: