探究java-JVM的五步(三步)类加载机制(包含类加载过程的一些代码书写,如类加载代码)

前言:

写了不少java代码,以前一直都是关心java程序跑起来后的情况步骤,今天我们就来探究一下:
在java程序还是一个个存在于静态.class文件中的类(也可以是任何被JVM认可的文件,但本质还是要的到类)的时候,JVM是在怎么将它们变成一个个存在于内存的对象的。

为此,JVM专门有一个类加载机制,用于处理.class文件加载到内存这个过程。

正文:

JVM类加载机制分为5个步骤:
1.加载
2.验证
3.准备
4.解析
5.初始化

分别简单介绍一下他们的概念,

1.加载
通常使用显式的加载Class.forName()
隐性加载:new对象时加载{在jvm开启后通过文件加载}

就是JVM通过一个文件得到一个类并在内存中进行生成java.lang.Class对象,一定在堆上。

既是通过文件加载到内存

注意,对于文件我们有一定的要求:
首先就是要满足JVM指定的规范,既是.class文件遵循的规范,
文件不一定是.class文件,JVM还能在jar包,war包一类的文件中获取到加载类需要的class信息,还有jsp文件也能通过动态代理产生一个可识别的class。

我们需要知道类加载器有三种:
启动类加载器(jdk的bin目录)
扩展类加载器(jdk的lib\ext目录)
应用程序类加载器(自定义的加载器,继承java.lang.ClassLoader)

它们的关系是双亲委派,什么是双亲委派,就是当自己需要完成一个任务时不先自己考虑能否处理,先向上一代抛出任务,直到顶层(既是启动类加载器),然后再判断每个层能否处理这个任务**(在自己路径下能否找到类)**,不能则向下抛,直到能够处理为止

我们举个栗子,java.lang.Object对象位于rt.jar,是jdk\bin目录下的,该启动类加载器管理,我们都知道不管哪个对象都可以认为是Object对象。
那是因为不管哪一级的加载器想要加载Object对象
在双亲委派模式下都会被委托给启动类加载器,始终能得到同一个Object对象。

探究java-JVM的五步(三步)类加载机制(包含类加载过程的一些代码书写,如类加载代码)_第1张图片

好了,现在我们在不重写的情况下实现一个自己的类加载器(此处被封装为一个方法,功能类似于Class.forName())
首先准备一个.class文件,源码功能如下:

package com.j.test;

/**
 * @Description 作为类加载的实验对象
 * @Date 2020-06-10 -- 20:27
 * @Author joker
 * @Version 1.0
 */
public class PrintJVMLoad {
    public static void main(String[] args) {

    }
    public void loadTest() {
        System.out.println("我是被用作JVM类加载机制研究的类--类加载器");
    }
}

然后封装一个方法(加载到类并返回):

/**
    * @Description: 手动加载一个.class文件(以test.PrintJVMLoad为例,其他的不做实验)
     * ;类加载器
    * @Param:
    * @Return: 加载到的类
    * @Date: 2020/6/11 0011
    */
    public static Object loadClassByFile(String Filename){

        //class文件位置
        String constUrl="\\target\\classes\\com\\j\\test";
        Object object=null;
        try {
            //URL
            URL url = new URL("file:\\"+System.getProperty("user.dir")+constUrl);
            //得到加载器
            URLClassLoader loader=new URLClassLoader(new URL[]{url});
            //得到对应class对象
            Class c = loader.loadClass(Filename);
            //得到对象
            object = c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return object;

    }

然后检验是否加载成功:

    //测试类加载,一定是完整的java-package路径
        Object o=JVMClassLoad.loadClassByFile("com.j.test.PrintJVMLoad");

        if (o!=null){
            ((PrintJVMLoad)o).loadTest();
        }else {
            System.out.println("类加载失败");
        }

运行结果如下:
探究java-JVM的五步(三步)类加载机制(包含类加载过程的一些代码书写,如类加载代码)_第2张图片
方法被成功调用,成功了

2.验证
验证,准备,解析三步合称链接。

验证是为了是的.class文件是被JVM认可的,不危害JVM的。

3.准备

通常是为了将定义的类成员变量(静态的,在方法区)进行一个赋值或者初始化。
例如:

public static int a=80

在准备阶段,其仅仅会将a设为0,而不是80,80这个赋值操作将在初始化时放在类构造器()执行

public static final int a=80

此时会直接将a赋值为80,
因为此时在编译时变量a会产生一个ConstantValue属性,是直接赋值的依据。

4.解析
是JVM通过常量池将符号引用转为直接引用的过程

符号引用不一定存在于内存中(符号引用是一种.class文件规范规定),但直接引用是一个指针,一定指向了一个具体地址,一定在内存中。

这么理解就容易了
就是将.class文件中的规范翻译成JVM认得的类构造规范(常量池的那些常量)
既是—翻译

5.初始化

此时以执行类构造器方法()为主。

此时在类构造器方法中会执行类似对类成员赋值(准备阶段的传递的任务),静态的代码。
类构造器遵循继承原则,在父类完成执行后,才执行子类。

一般无静态模块(任何静态的变量,代码块)的方法可不执行类构造器方法。
显式的调用也不会。

//////////////////////////////////////////////////////////////

JVM中的类加载机制大抵是这样了。

感谢耐心的阅读,希望能帮到你。

//////////////////////////////////////////////////////////////

你可能感兴趣的:(JVM系统学习,java,技术性)