JVM virtual machine --- (14) class loading mechanism

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/qq1021979964/article/details/98214894

        The virtual machine data that describes the classes loaded from Class file into memory, and verify the data, and converts parsing initialization, forming Java type can be used as a virtual machine, which is a virtual machine class loading mechanism.

First, the class loading time

        Classes begin to be loaded into the virtual machine memory, to unload a far memory, its entire life cycle, including: Load (Loading), verification (Verification), preparation (Preparation), analytical (Resolution), initializing (Initialization), use (Using) and unloading (unloading) 7 stages. Wherein the verification, preparation, referred to as analytical connecting portion 3 (Linking).

 

Only five cases have to initialize the class immediately (while loading, validation, preparation naturally need to start before this):

1.遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

5.当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

        For these five scenarios will trigger class is initialized, the virtual machine specification uses a very strong qualifier: "one and only", these five scenarios for the behavior of a class called for active reference and, apart from the in addition, all references to class methods are not trigger initialization, known as passive reference.

Verification 1. Example 3, 4

father

public class Parent {
    static {
        System.out.println("parent 初始化了...");
    }
}

Subclass

public class Child extends Parent {

    static {
        System.out.println("child 初始化...");
    }

    public static void main(String[] args) {

    }

}

Output:

Analysis: execution class contains main () initializes the class (article 4), during initialization subclass, found that the parent class is not initialized, the parent class is initialized (article 3.)

 

2. The case does not trigger initialization

a case: static field references the parent class by sub-category, sub-category will not be initialized

father

public class Parent {

    static {
        System.out.println("parent 初始化了...");
    }

    public static int num = 10;
}

Subclass

public class Child extends Parent {

    static {
        System.out.println("child 初始化...");
    }

}

Execution class

public class Main {

    public static void main(String[] args) {
        System.out.println(Child.num);
    }
}

Initialize the parent class, subclass but not initialized.

 

. B Case: to refer to a class defined by an array of

The above code execution class to do some modifications, subclass and parent classes do not change.

Execution class

public class Main {

    public static void main(String[] args) {
        Child[] children = new Child[10];
    }
}

Not initialize the subclass and superclass.

 

Case c: constant call class.

The execution classes and subclasses modify the parent class is not altered.

Subclass

public class Child extends Parent {

    public static final int a = 20;

    static {
        System.out.println("child 初始化...");
    }

}

Execution class

public class Main {

    public static void main(String[] args) {
        System.out.println(Child.a);
    }
}

 

Second, the class loading process

1. class loading process of loading ---

"Load" is "class loader" a stage (Class Loading) process, in the loading phase, the virtual machine needs to complete the following three things:

1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

The three-point demand virtual machine specification is not specific. So virtual machine implementation and flexibility specific applications are quite large.

For example, the first: not specified binary byte stream from a Class file to obtain accurate is simply not indicate where to get, how to get. E.g:

1.从ZIP包中读取(典型场景:JAR、EAR、WAR)。
2.从网络中获取(典型场景:Applet)。
3.运行时计算生成(典型场景:动态代理技术,*$Proxy)。
4.由其它文件生成(典型场景:JSP应用)。
5.从数据库读取(典型场景:中间服务器)。

        Particularity array class: the array itself is not created by the class loader, which is created directly by the Java virtual machine, but an array of classes and class loader still have a very close relationship, because the element type array class ultimately depends on the class loader to create an array creation process is as follows:

1.如果数组的组件类型是引用类型,那就递归采用类加载来加载。
2.如果数组的组件类型不是引用类型,Java虚拟机会把数组标记为引导类加载器关联。
3.数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为public。

No need to wait until the class loader "first use" class when the class is loaded, Java Virtual Machine Specification allows the system to pre-load some classes.

        After the completion of the loading stage, external to the virtual machine according to a binary stream of bytes required to store the virtual machine in the format area method, area method implemented data storage format defined by the virtual machine itself, not the virtual machine specification predetermined subregions specific data structures.

Then a memory to instantiate an object of class java.lang.Class method area stored inside, the external interface access method as a program area of ​​these types of data.

       Part of the load phase and the connection phase (e.g., a portion of the file format byte code verification operation) is carried out crossing, loading stage has not been completed, the connection phase may have started, but they caught in the operation stage, the stage is still connected content start time these two stages remained fixed sequence.

2. class loading verification process ---

The first step is to verify the connection phase, the purpose of this stage is to ensure that the information byte stream file included in Class meet the requirements of the current virtual machine and the virtual machine will not jeopardize their own safety.

i. verification file format

The first stage to verify whether the byte stream compliant Class file format, and can be the current version of the virtual machine process (based on a binary byte stream). This phase may include the following verification points:

1.是否以魔数0xCAFEBABE开头。
2.主、次版本号是否在当前虚拟机处理范围之内。
3.常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
4.指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
5.CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据。
6.Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
……

The main purpose of this verification is to ensure that the input stage of the byte stream can properly parse and store the zone method, Java fit the description of a type of information format requirements.

ii. metadata validation

The second stage is the byte code description information semantic analysis to ensure that the information which meets the requirements described in the Java language specification (configuration based on the method of storage area). This phase may include verification points are as follows:

1.这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
2.这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
3.如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
4.类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合 规则的方法重载,例5.如方法参数都一致,但返回值类型却不同等)。
……

       The main purpose of the second phase of authentication is metadata information such semantic verification, to ensure that metadata information does not comply with the Java language specification does not exist.

iii. bytecode verification

After metadata information in the data type of finish checking, this stage of the process will be verified analysis class body in the second stage, ensure the method validation class at runtime will not make harm virtual machine security event (structure-based storage area method), for example:

1.保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中。
2.保证跳转指令不会跳转到方法体以外的字节码指令上。
3.保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险和不合法的。
……

The third stage is the entire validation process is the most complex stage, the main purpose is the data flow and control flow analysis to determine the semantics of the program is legitimate, logical.

iv. verification of symbolic references

Verification of symbolic references can be seen as information other than the type itself (various symbols constant pool reference) matching check, usually you need to verify the following:

1.符号引用中通过字符串描述的全限定名是否能找到对应的类。
2.在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
3.符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
……

Final stage check occurs in the virtual machine when the symbol references into direct reference, the conversion operation in the third phase of the connection - Analytical phase occurs.

         The purpose is to ensure the verification of symbolic references analysis operation can be performed properly. If not verified by reference to a symbol, it will throw an anomaly java.lang.IncompatibleClassChangeError subclasses, such as java.lang.IllegalAccessError, java.lang.NoSuchFieldError, java .lang.NoSuchMethodError and so on.

3. Preparation class loading process ---

Preparation phase is formally allocate memory for class variables and set the stage class variable initial values ​​of these variables are needed memory will be allocated in the method area.

public static int value = 1024;

That variable value after the preparatory phase of the initial value of 0 instead of 1024, because at that time not yet started performing any Java method, and the value assigned to putstatic Directive 1024 after the program is compiled, stored in a class constructor <clinit> among () method, so the value assigned to the action before 1024 will be executed in the initialization phase.

Substantially zero value for all data types

Special case: if the present field attribute table ConstantValue class attribute field, and that in the preparation stage variable value is initialized to the value specified by the property ConstantValue.

public static final int value = 1024;

编译时Javac会为value生产ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为1024。

4.类加载的过程---解析

解析阶段是虚拟机将常量池内的符号引用替换成直接引用的过程。

符号引用:

        符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机实现的布局无关,引用的目标并不一定已经加载到内存中。

直接引用:

       直接引用可以是直接指向目标的指针,相对偏移量或是一个能间接定义到目标的句柄,直接引用是和虚拟机内存布局相关,引用的布标必定在内存中存在。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。

分别对应于常量池的

CONSTANT_Class_info

CONSTANT_Fieldref_info

CONSTANT_Methodref_info

CONSTANT_InterfaceMethodref_info

CONSTANT_MethodType_info

CONSTANT_MethodHandle_info

CONSTANT_InvokeDynamic_info 7种常量类型

5.类加载的过程---初始化

       类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段可以自定义类加载器参与之外,其余都是由虚拟机主导与控制,到了初始化阶段,才真正开始执行类中定义的Java程序代码(字节码)。

从另一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。

       如果执行<clinit>()方法那条线程退出后,其它线程唤醒之后也不会再次进行<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。

三、类加载器

类加载器:通过一个类的全限定名来获取描述此类的二进制字节流。

1.类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。

比较两个类是否相等,需要这两个类是由同一个类加载器加载的前提之下才有意义。否则哪怕是同一个Class文件,这两个类一定不相等。

相等指的是包括Class对象的equals()方法,isAsssignableFrom()方法,isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。
  • i. 自定义类加载器

  1. 定义一个类,继承ClassLoader
  2. 重写loadClass方法
  3. 实例化Class对象
  • ii. 自定义类加载器的优势

  1. 类加载器是Java语言的一项创新,也是Java语言流行的重要原因之一,最初的设计是为了满足Java Applet的需求而开发出来的。
  2. 高度的灵活性
  3. 通过自定义类加载器可以实现热部署
  4. 代码加密
  • iii. 自定义类加载器案例

package com.kevin.jvm.classloader;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author caonanqing
 * @version 1.0
 * @description     测试自定义类加载器
 *      1.定义一个类,继承ClassLoader
 *      2.重写loadClass方法
 *      3.实例化Class对象
 * @createDate 2019/8/2
 */
public class ClassLoaderDemo {

    public static void main(String[] args) throws Exception {

        // 自定义类加载器
        ClassLoader mycl = new ClassLoader() {

            /**
             *  加载当前包下的所有类,其他的类委派父类加载器
             * @param name  类的全限定名
             * @return
             * @throws ClassNotFoundException   找不到类的时候会抛出这个异常
             */
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                // com.kevin.jvm.classloader.ClassLoaderDemo
                String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream ins = getClass().getResourceAsStream(filename); // 加载
                if(ins == null) {   // 没有找到,让父类加载器来加载
                    return super.loadClass(name);
                }
                try {
                    byte[] buff = new byte[ins.available()];
                    ins.read(buff);
                    return defineClass(name,buff,0,buff.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };
        // 有main方法的类,被会应用程序加载器加载一次,加上我们自定义的加载器,所以会被加载两次
        Object c = mycl.loadClass("com.kevin.jvm.classloader.ClassLoaderDemo").newInstance();
        System.out.println(c.getClass());
        // 其实c是被应用程序加载类所加载的,所以他们并不是被同一个类加载器加载的、
        System.out.println(c instanceof ClassLoaderDemo);

    }
}

2.双亲委派模型

从Java虚拟机的角度来讲,只存在两种不同的类加载器:

1.一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分。
2.另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

从Java开发人员的角度来看,大部分Java程序会使用到以下3中系统提供的类加载器:

1.启动类加载器(Bootstrap ClassLoader):加载lib下或被-Xbootclasspath路径下的类。
2.扩展类加载器(Extension ClassLoader):加载lib/ext或java.ext.dirs系统变量所指定的路径中的所有类库。
3.应用程序类加载器(Application ClassLoader):ClassLoader负责,加载用户路径(ClassPath)上所指定的类库,一般也成为系统类加载器,默认使用这个。

        一般我们的应用程序都是这3种类加载器互相配合进行加载的,如果有必要还可以加入我们自定义的类加载器。这些类加载器之间的关系,称为类加载的双亲委派模型。

如图所示:

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

这里类加载器之间的父子关系一般不会以继承的关系来实现,而是都使用组合关系来复用类加载器的代码。

工作流程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器。只有父类加载器无法完成时子类加载器才会尝试加载。

a.案例

package java.lang;

/**
 * @author caonanqing
 * @version 1.0
 * @description         测试双亲类加载器
 * @createDate 2019/8/2
 */
public class Object {

    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println("Hello World");
    }
}

这个类其实并没有被加载。

b.双亲类加载器的案例

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        //检查该类是否已经加载过
        Class c = findLoadedClass(name);
        if (c == null) {
            //如果该类没有加载,则进入该分支
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //当父类的加载器不为空,则通过父类的loadClass来加载该类
                    c = parent.loadClass(name, false);
                } else {
                    //当父类的加载器为空,则调用启动类加载器来加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //非空父类的类加载器无法找到相应的类,则抛出异常
            }

            if (c == null) {
                //当父类加载器无法加载时,则调用findClass方法来加载该类
                long t1 = System.nanoTime();
                c = findClass(name); //用户可通过覆写该方法,来自定义类加载器

                //用于统计类加载器相关的信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            //对类进行link操作
            resolveClass(c);
        }
        return c;
    }
}

3.破坏双亲委派模型

双亲委派模型并不是强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。

        线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还没设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索:

1.将以java.*开头的类委派给父类加载器加载。
2.否则,将委派列表名单内的类委派给父类加载器加载。
3.否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
4.否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
5.否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的 类加载器加载。
6.否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7.否则,类查找失败。 上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类 加载器中进行的。

 

Guess you like

Origin blog.csdn.net/qq1021979964/article/details/98214894