A text to read Java class loading mechanism

Java class loading mechanism

Detailed Java class loading mechanism. @pdai

Class life cycle

Class loading process which includes 加载, 验证, 准备, 解析, 初始化five stages. In these five stages, 加载, 验证, 准备and 初始化the order of these four phases occur is determined, and 解析tied stage is not necessarily, it can start after the initialization phase, in some cases, this is to support the Java language run time set (also called dynamic binding or late binding) . Also note that there are several stages of the start sequence, rather than complete or sequentially, because these stages are generally intersect one another mixing typically call or activate a stage at another stage during execution.

Class load: find and load the class of binary data

The first stage loaded class load in the loading phase, the virtual machine needs to do three things:

  • To obtain a binary byte stream, which is defined by the fully qualified name of a class.
  • This represents the byte stream of static storage structure into a run-time data structure area method.
  • Generating a representative of the Java heap java.lang.Class object of this class, such as a method of access to the data area entry.

Relative to other stages of class loading, loading phase (specifically, the operation is a binary stream of bytes acquired class loading phase) is controllable strongest stage , because the developer can use the system to provide both class loader is to finish loading, you can also customize your own class loader to finish loading.

After loading phase is completed, the external storage format of virtual machines on a binary stream of bytes required for virtual machines in accordance with the method area, but also create a heap in Java java.lang.Classobject classes so that you can access through the target zone method these data.

Class loader does not need to wait until it is loaded, JVM specification when a class is the "first active use" allows the class loader when a class is expected to be used, it is pre-loaded with it, if encountered in the process of pre-loaded in .class file is missing or there is an error, the class loader when the class must first take the initiative to use the report error (LinkageError error) in the program if the class has not been actively using the program, the class loader will not report an error.

Load .class files the way

  • Loaded directly from the local system
  • .Class files downloaded through the network
  • Load .class files from the zip, jar archive, etc.
  • .Class extract files from a proprietary database
  • The Java source files dynamically compiled into .class files

connection

Verification: ensure the correctness of the loaded class

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. Validation phase will be completed roughly an inspection operation four stages:

  • 文件格式验证: Verify byte stream for compliance with Class file format; for example: whether to 0xCAFEBABEbegin with, whether the major and minor version numbers in the current processing range of the virtual machine, if there is the constant in the constant pool type is not supported.

  • 元数据验证: Bytecode information described semantic analysis (Note: Comparative javacsemantic analysis phase of the compilation), which describes the information to ensure compliance with the requirements of the Java language specification; for example: whether a parent class, in addition to java.lang.Objectoutside.

  • 字节码验证: The data flow and control flow analysis to determine the semantics of the program is legitimate, logical.

  • 符号引用验证: Make sure that the analysis operation is performed properly.

Validation phase is very important, but not necessarily, has no effect on the program run, if the referenced class after repeated verification, consider using -Xverifynoneparameter to shut down most of the class verification measures to shorten the virtual machine class loader time.

Preparation: allocate memory for the class static variable, and initializes its default values

Preparation phase is formally allocate memory for class variables and set the stage class variable initial values, this memory will be assigned in the method area . The following points need to pay attention to this stage:

  • This time includes only the memory allocation class variables ( static), and does not include the instance variables, instance variables in the heap assigned as a Java objects when an object is instantiated.
  • Typically the initial value set here is the data type of the default value of zero (e.g. 0, 0L, null, falseetc.), rather than the values are given explicitly in Java code.

    Suppose a class variable is defined as: public static int value = 3; then the initial value of the variable value after the preparation phase 0, instead 3, because this time has not yet started any Java method, and the value assigned to the 3 put staticinstruction after the program is compiled, stored in a class constructor <clinit>()in a method, so the value assigned to the operation 3 will be performed only in the initialization phase.

There is also need to pay attention to the following points

  • The basic data types, for the class variable (static) and global variables, if not explicitly assign its direct use, the system will assign it a default value of zero, and for the local variables, before use you must explicitly assign it, or do not compile time.
  • For simultaneously staticand finalmodified constants, it must be explicitly assigned at the time of its declaration, otherwise it does not compile time; only the final modification of the constants you can either explicitly assign it in the statement, also in explicitly assign a value to the case of initialization, in short, must be explicitly assigned before its use, the system does not give them a default value of zero.
  • For reference data types reference, the array as a reference, object reference, etc., if not explicitly assign its direct use, imparting a zero value for which the system will default, ie null.
  • If no value is assigned to each element in the array during array initialization, wherein the element will then be given a default value of zero in accordance with the corresponding data type.
  • If ConstantValue property class field attribute table present field, i.e. while being modified final and static, it will be initialized to the value specified in the attribute ConstValue preparation stage variable value. Suppose the above class variable value is defined as: public static final int value = 3;the Javac ConstantValue attribute value will be generated at compile time, during the preparation phase in accordance with the virtual machine will be provided ConstantValue value '3. We can be understood as static finala constant at compile time will result into a constant pool to call its class
Analysis: The class is converted into direct reference symbol references

The resolution phase is the constant pool of the virtual machine process is replaced symbolic references directly referenced, mainly for the analysis operation , or 接口, 字段, 类方法, 接口方法, 方法类型, 方法句柄and 调用点qualifiers for symbolic references 7 classes. Symbolic reference is a set of symbols to describe the target can be any literal.

直接引用It is a direct pointer to the object, an indirect or offset relative to the positioning target handle.

initialization

Initialization, given the correct initial value for the class of static variables, JVM classes responsible for initializing the main class variables are initialized. Initial value set in the Java class variable in two ways:

  • Declare a class variable is specified initial value
  • Using static code block is assigned an initial value for class variables

JVM initialization step

  • If this class has not been loaded and connected, and then connected to a program loads the class
  • If the direct parent class has not been initialized, it first initializes its direct parent
  • If the class initialization statements, the system sequentially performs the initialization statements

Class initialization timing : only when the active use of the class initialization will lead the class, the class actively used include the following six:

  • Create an instance of the class, which is the new way
  • Access static variables of a class or interface, or assignment of the static variables
  • Static method call class
  • Reflector (e.g. Class.forName ( "com.pdai.jvm.Test"))
  • Initialize the subclass of a class, the parent class will be initialized
  • When the Java virtual machine started to be marked to start class class (Java Test), to run a master class directly java.exe command

use

The method of accessing data structures class interface region, the object region data Heap.

Uninstall

Java virtual machine will end the cycle of life situations

  • Implementation of the System.exit () method
  • Normal program execution ends
  • Encountered an exception or error during execution aborted
  • Because the operating system error caused the process to terminate the Java Virtual Machine

Class loader, JVM class loading mechanism

Class loader hierarchy

Note: This is not the parent class loader through inheritance to achieve, instead of using a combination of.

Standing on the perspective of the Java virtual machine is concerned, there are only two different class loaders: Start class loader: it uses the C ++ implementation (here only Hotspot, that is, the default virtual machine after JDK1.5, there are many other virtual machine is implemented in the Java language), is part of the virtual machine itself; all other class loaders: these classloaders by Java language, independent of the outside of the virtual machine, and all derived from the abstract class java.lang.ClassLoader, the class loader after needs to be loaded into memory by the boot class loader to load to other classes.

Standing Java developer's perspective view, the class loader can be roughly divided into the following three categories:

启动类加载器: Bootstrap ClassLoader, is responsible for loading stored in jre lib (on behalf of JDK JDK installation directory, the same below) under JDK \ \, or -Xbootclasspath parameter specifies the path of the virtual machine and can be identified by library (such as rt. jar, all the java. * classes are beginning Bootstrap ClassLoader load). Start class loader is not directly referenced Java program.

扩展类加载器: Extension ClassLoader, the loader by the sun.misc.Launcher$ExtClassLoaderrealization that it is responsible for loading JDK \ jre \ lib \ ext directory or all java.ext.dirs library specified by the system variable path, (such as the beginning of the javax * classes.) developers can directly use the extension class loader.

应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:

  • 在执行非置信代码之前,自动验证数字签名。
  • 动态地创建符合用户特定需要的定制化构建类。
  • 从特定的场所取得java class,例如数据库中和网络中。

寻找类加载器

寻找类加载器小例子如下:

package com.pdai.jvm.classloader;
public class ClassLoaderTest {
     public static void main(String[] args) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        System.out.println(loader);
        System.out.println(loader.getParent());
        System.out.println(loader.getParent().getParent());
    }
}

结果如下:

sun.misc.Launcher$AppClassLoader@64fef26a
sun.misc.Launcher$ExtClassLoader@1ddd40f3
null

从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是BootstrapLoader(引导类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null

类的加载

类加载有三种方式:

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

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

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

package com.pdai.jvm.classloader;
public class loaderTest { 
        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()和ClassLoader.loadClass()区别?

  • Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
  • ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
  • Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

JVM类加载机制

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

  • 双亲委派机制, 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
    双亲委派机制
    1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
    2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
    3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
    4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

双亲委派代码实现

public Class<?> loadClass(String name)throws ClassNotFoundException {
            return loadClass(name, false);
    }
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
            // 首先判断该类型是否已经被加载
            Class c = findLoadedClass(name);
            if (c == null) {
                //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
                try {
                    if (parent != null) {
                         //如果存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                    //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
                 // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }

双亲委派优势

  • 系统类防止内存中出现多份同样的字节码
  • 保证Java程序安全稳定运行

自定义类加载器

通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java 类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自 ClassLoader 类,从上面对 loadClass 方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:

package com.pdai.jvm.classloader;
import java.io.*;

public class MyClassLoader extends ClassLoader {

    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
        String fileName = root + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
        return root;
    }

    public void setRoot(String root) {
        this.root = root;
    }

    public static void main(String[] args)  {

        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("D:\\temp");

        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("com.pdai.jvm.classloader.Test2");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。

这里有几点需要注意:

1、这里传递的文件名需要是类的全限定性名称,即com.pdai.jvm.classloader.Test2格式的,因为 defineClass 方法是按这种格式进行处理的。

2、最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。

3、这类Test 类本身可以被 AppClassLoader 类加载,因此我们不能把com/pdai/jvm/classloader/Test2.class 放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过我们自定义类加载器来加载。

参考文章

  • http://www.cnblogs.com/ityouknow/p/5603287.html

  • http://blog.csdn.net/ns_code/article/details/17881581

  • https://segmentfault.com/a/1190000005608960

  • http://www.importnew.com/18548.html

  • http://zyjustin9.iteye.com/blog/2092131

  • http://www.codeceo.com/article/java-class-loader-learn.html

最全的Java后端知识体系 https://www.pdai.tech, 每天更新中...

Guess you like

Origin www.cnblogs.com/pengdai/p/11774988.html