Notes depth understanding of the Java Virtual Machine (iv) VM class loading mechanism

Outline

The virtual machine type data described in the class file to be loaded from memory, and to verify the data, and converts parsing initialization, forming Java type can be used as a virtual machine, the virtual machine that is the class loading mechanism

Class loading time

The life cycle

Loading, validation, preparation, parsing, initialization, use and uninstall
Here Insert Picture Description

Loading, validation, preparation, initialization sequence and unloading is determined, class loading process must begin in this order, resolution phase is not necessarily analytical phase can begin after the initialization phase, in some cases, this is to support when you run the Java language bindings.

Note the beginning, instead of or completion of these stages are usually carried out cross-mixing with each other, usually calls during a stage of implementation, the activation of another phase.

Virtual machine specification defines one and five kinds of situations must be initialized

  1. Encounter new, getstatic, putstatic invokestatic or 4 when this bytecode instruction, if the class is not been initialized, initialization is required to trigger it. This generates four instructions of the most common scenario is the Java code: using the new keyword to instantiate an object when reading or setting a static field of a class (the final modification, the results have been compiled into a constant pool of static except) when the field, as well as calling a static method of class time.
  2. When using java.lang.reflect package approach to reflect the class called, if the class is not been initialized, you need to trigger its initialization.
  3. When initializing a class, if you find the parent class has not been initialized, you need to trigger initialization of the parent class.
  4. When the virtual machine starts, the user needs to specify a main class to be executed (contains main () method of the class), the virtual machine to initialize this master class.
  5. When using a dynamic language support JDK 1.7, if a java.lang.invoke.MethodHandle instance final analytical results REF_getStatic, REF_putStatic, REF_invokeStatic way to handle, and this method handle the corresponding class not been initialized, you need to trigger its initialization.

These five actions are called for active reference to a class In addition, the reference class manner will not trigger initialization, known as passive reference.

Examples of passive reference

Example 1

Reference a static field of the parent class by subclass does not lead to initialize the subclass

Here Insert Picture Description
Here Insert Picture Description
Here Insert Picture Description
Here Insert Picture Description

Example 2

Here Insert Picture Description
Here Insert Picture Description

Example 3

Here Insert Picture Description
Here Insert Picture Description
Here Insert Picture Description
Here Insert Picture Description

Constant reference to the class which has been transformed into constant references to their own production

note

Loading process interfaces and classes are a bit different: When a class is initialized, all the requirements of its parent class has been initialized, but an interface at initialization, the interface does not require the parent to complete all initialization, only genuine use to the parent when the interface will initialize.

Class loading process

load

A load stage is the class loading process, loading stage virtual machine needs to be done:

  1. To obtain such a binary byte stream defined by the fully qualified name of a class
  2. This represents the byte stream of static storage structure into a run-time data structure area method
  3. Generating in the memory access entry represents java.lang.Class object of this class, this class region as a method of various data structures

Binary byte stream and does not limit the file acquired from a Class, can be obtained from the package, obtained from the network, calculating and obtaining the runtime, acquires another file ...

For an array, the data type itself is not created by the class loader, which created directly by the virtual machine. But the element type array class still need to create a class loader.

Java.lang.Class memory instantiated objects not necessarily in the stack, HotSpot zone method in which

Loading stage and the connecting part are cross carried. But the start order is still loaded first and then connect

verification

The purpose of verification 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 harm

Whether rigorous validation phase, will determine whether the Java virtual machine can withstand malicious code attacks

Validation phase probably will be divided into four phases of inspection work: file format validation, metadata validation, bytecode verification, verification of symbolic references

File format validation

  • Are begin with the magic number

  • Whether within the virtual machine processing range of major and minor version number

  • Are Constants pool is not supported by type

This verification stage is performed in a binary byte stream, and only after the verification by this stage of the method of the byte stream will enter the region of memory for storage, so the back of the validation phase area method is based on the storage structure of

Metadata validation

Byte code information described semantic analysis, to ensure compliance with the information that describes the Java Language Specification

  • Is there a parent

  • Are not allowed to inherit the parent class is inherited class

  • If it is an abstract class, whether to implement all the methods

主要目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息

字节码验证

通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的,这阶段将对方法体进行校验分析,确保运行时不会出现危害虚拟机的事情。

  • 保证数据类型和指令代码序列可以配合工作

  • 保证跳转指令不会跳转到方法体以为的字节码指令上

符号引用验证

符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段----解析阶段中发生。

符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验

  • 符号引用中通过字符串描述的全限定名是否能找到对应的类

  • 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段

  • 符合引用中的类,字段,方法的访问性是否可被当前类访问

符号验证确保解析动作能够正常执行,如果所运行的全部代码都已经被反复使用和验证过,那么在实施阶段就可以考虑关闭大部分类验证措施,缩短虚拟机类加载的时间。

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,类变量所使用的内存都将在方法区中分配。此时的分配仅仅只有类变量,实例对象变量之后再堆中分配,一般情况下类变量会初始化为零值,不管其有没有被赋值。赋值在初始化阶段才会发生。(执行类构造器的时候)

Here Insert Picture Description
不过,如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值。

例如:

Here Insert Picture Description
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。

解析

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

  • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
  • 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。

几种解析方式:

  • 类或接口解析
  • 字段解析
  • 类方法解析
  • 接口方法解析

初始化

初始化阶段是执行类构造器()方法的过程

  • <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问,如代码清单7-5中的例子所示。
  • <clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。
  • 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
  • <clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
  • 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
  • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞[2],在实际应用中这种阻塞往往是很隐蔽的。代码清单7-7演示了这种场景。

只会被执行一次

类加载器

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj=myLoader.loadClass("load.load.ClassLoaderTest").newInstance();

        System.out.println(obj.getClass());
        System.out.println(obj instanceof load.load.ClassLoaderTest);
    }
}

Here Insert Picture Description

不同加载器加载,不一样

双亲委派模型

Here Insert Picture Description

  • 启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类将器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可。
  • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher $AppClassLoader实现。于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

Here Insert Picture Description

好处:基础类只会加载一个,不会加载出用户自己写的同名类。

即:双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载)

破坏双亲委派模型

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

主要出现过3次大规模的"被破坏“情况

  • 第一次:双亲委派模型出现之前,用户通过重写loadClass方法自定义类加载器

  • 第二次:基础类又要调用回用户的代码(SPI)

    由于虚拟机加载中,要解析类(设为A)中的类或者接口(设为B)用到的类加载器是A的类加载器

    而SPI需要的实现类并非BootstrapLoader能够找到的(不在jdk目录中),所以需要打破双亲委派模型

  • Third: the user program (hot deployment) the pursuit of dynamics

    Since the same class loader will load the same class once, but also after the restart modify the code, the new code to take effect, therefore, to complete the hot deployment you need to replace the class loader at the same time replace the code, breaking the parent delegation model

Guess you like

Origin blog.csdn.net/weixin_43958969/article/details/95314720