类加载机制

Java知识点总结:想看的可以从这里进入

1、简介


Java老生常谈的一个优点就是跨平台,一次编写到处运行。其核心就是JVM。那JVM到底是什么?

我们编写Java用的是Java语言,而计算机呢?它底层上只知道0和1,而且不同平台之间语言不互通,所以程序我们能看懂,但对计算机来说它就是一堆乱码。C语言虽然可以跨平台,但还是很麻烦的,它需要重新编译适配,所以Java就索性在源代码和操作平台之间添加一个中转站,由它运行Java编译后的字节码,再根据不同平台翻译成相应的计算机语言,这就是JVM。

JVM它就是一个虚拟的计算机,连接在源代码和计算机之间,它可以识别.class的字节码文件,在运行后将字节码文件翻译成对应不同操作平台的语言供计算机读取。所以只要虚拟机可以根据不同操作平台可以翻译成不同的计算机语言,就可以实现在不同平台运行程序了。

在这里插入图片描述

Java代码的运行机制就是:程序源代码.java→jdk的编译器→字节码文件.class→虚拟机(JVM)翻译器→计算机语言→计算机读取。

1、JDK(Java Development Kit):开发者工具包,是开发java程序的关键,包含了JRE,Java开发+运行。

2、JRE(Java Runtime Environment):java运行时环境,包含JVM,Java运行。

3、JVM(Java Virtual Machine):java虚拟机,是java跨平台的核心部分。

2、类加载机制


2.1、类加载流程

程序在运行期间需要使用某个时(类、接口、枚举等),如果内存中还没有,就需要进行类加载,通过加载、连接、初始化三个步骤完成类加载。JVM是允许在使用类之前就先进行预加载 ,但是类如果有错误,在预加载期间不会保错,只有程序需要使用类时才会报出错误。

JVM不能直接运行Java代码,需要JDK中的编译器把.java的文件编译成.class的文件后,JVM才能载入。

  1. 加载:将类.class文件中的二进制数据读取到JVM中,存放在方法区中(1.8前永久代、1.8后元空间),同时在堆中生成一个代表这个类的 java.lang.Class对象,Class对象封装了类的数据结构,并提供访问方法区中数据的接口(反射)。

    第一个使用类时产生class对象,后续每次new这个类时产生的对象,就是以这个Class对象为模板产生的。

  2. 连接:把内存中的数据合并到虚拟机的运行时环境中

    • 验证:确保加载的字节流中包含的信息符合JVM的规范。
      文件格式验证:验证字节流是否符合规范
      元数据验证:对字节码的信息进行语义分析,确保描述信息符合规范。
      字节码验证:确定程序的语义是否符合规范
      符号引用验证:确保能正常解析

    • 准备:为类变量分配内存(static修饰的),并将其初始化为默认值。但是被final static修饰类变量的会直接赋正确的值,而不是默认值(因为final static变量被赋值后就不可改)。

    • 解析:将常量池内的符号引用替换成直接引用。

      符号引用:用一组符号来表示目标
      直接引用:直接指向目标的指针、可以定位到目标的句柄。

  3. 初始化:Java虚拟机执行类的初始化语句,会赋正确的初始值。如果父类没有初始化会先初始化父类

    (Java在编译成字节码后没有构造方法的概念,只有类初始化方法和对象初始化方法。
    类初始化方法是类变量赋值语句、静态代码块。
    对象初始化方法是成员变量赋值语句、普通代码块、最后是构造方法,但是如果没有进行对象初始化操作(没有new对象)就不会执行对象初始化方法。)

    • 初始化父类静态成员和static语句块

    • 子类静态成员和static语句块

    • 父类普通成员和非static语句块

    • 父类构造函数

    • 子类普通成员和非static语句块

    • 子类构造函数

只要对类主动使用时才会触发初始化:

1、new

2、访问类的静态变量、对类静态变量赋值

3、调用类的静态方法

4、反射机制

5、初始化类的子类,父类也会初始化

6、启动类或Main方法的类首先初始化

2.2、类加载器

类加载器是负责将类的.class文件加载到JVM中的,加载后会生成一个对应的 java.lang.class对象,而一个类一旦被JVM加载后,就不会被再次加载了(类的全限定名+类加载器构成一个唯一的标识符)。JVM采用的是按需加载,只要需要用到某个类时,才会对这个类进行加载。

在这里插入图片描述

JDK有三个自带的类加载器:

  1. BootsStrapClassLoader启动类加载器:%JAVA_HOME%/lib下的jar包和class文件。主要是JVM自身需要的类,是Java运行的核心类库。c++语言实现
  2. 扩展类加载器(原为ExtClassLoader,在JDK 1.9 之后,改名为 PlatformClassLoader):%JAVA_HOME%/lib/ext下的jar包和class文件。开发人员可直接获取使用
  3. AppClassLoader应用程序类加载器:加载classPath下的类文件,我们写的代码。

在这里插入图片描述

另外除了系统自带的加载器外,还可以自定义加载器。所有用户自定义类加载器都应该继承ClassLoader类,然后重写findClass方法

//自定义一个类加载器
public class MyClassLoader extends ClassLoader {
    //指定一个class文件的路径
    private String classPath;
    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //完整的类名
        String file = classPath + name + ".class";
        System.out.println(file);
        
        try (BufferedInputStream bis= new BufferedInputStream(new FileInputStream(file));
             ByteArrayOutputStream baos =new ByteArrayOutputStream(); ){
            int len;
            byte[] data=new byte[1024];
            while ((len=bis.read(data))!=-1){
                baos.write(data,0,len);
            }
            //获取内存中的字节数组
            byte[] bytes = baos.toByteArray();
            Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
            return clazz;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
//测试
public static void main(String[] args) {
    MyClassLoader myClassLoader = new MyClassLoader("D:/mydata/");
    try {
        Class<?> clazz = myClassLoader.loadClass("BubbleSort");
        System.out.println("类加载器为:"+clazz.getClassLoader().getClass().getName());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

2.3、双亲委派机制

JVM的三种加载机制:

  1. 全盘负责:当一个类加载器加载某个类时,此类所引用或依赖的其他类也由此类进行加载。
  2. 父类委托:一个加载器如果有父类加载器,则会把加载任务推脱给父类完成,只有父类无法完成加载时,子类才会自己去加载
  3. 缓存:被加载过的class文件会进入缓存中,当再需要加载此类时,会在缓存中获取,只有缓存不存在此类时,才会进行加载(想一下我们在编写代码时,每次修改完某个类时,是不是需要重新启动一下才会生效??就是这个机制)

双亲委派的机制:如果一个类加载器收到加载请求,它不会自己先加载,而是把请求委托给父类,如果父类还有父类加载器,则继续向上委托,直到达到最顶层的启动类加载器,启动类加载器可以加载,就成功返回Class,如果启动类加载器还无法完成加载,则子类再尝试自己加载,如果还加载失败,则会提示ClassNotFoundException异常。

双亲委派的优先级可以避免类不会被重复加载(全盘负责),还可以防止被核心类库被恶意篡改(如启动类加载器加载jar包内的文件,除非你直接修改本机的JDK,否则它不会加载其他恶意同名的文件)。

类如果被加载过,类加载会选择在缓存中取出,而不是重新加载。

//ClassLoader类源码,查看双亲委派

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先,检查类是否已经加载,如果已加载,就不需要加载了
        Class<?> c = findLoadedClass(name);
        //如果没有加载过,就看是否存在父类加载器
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 存在父加载器,就获取父类加载器,调用父类的loadClass方法(递归)
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //  // 没有父加载了,由BootstrapClassLoader加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                 // 如果没有找到就抛出ClassNotFoundException
                 // from the non-null parent class loader
            }
			// 如果仍然没有找到,则触发findclass,抛出classNotFoundException
            if (c == null) {
                long t1 = System.nanoTime();
                c = findClass(name);

                // 这是定义类装入器;记录数据
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

2.4、类的生命周期

Java虚拟机为Java程序提供运行环境,同时管理类和对象的生命周期:从类加载开始到类卸载结束。

加载→验证→准备→解析→初始化→使用→卸载。

Java每运行一个程序就会启动一个Java虚拟机进程,这个进程从启动到结束的过程就是生命周期,以下几种情况虚拟机会结束生命周期:

  • 程序执行结束

  • 执行过程中出现异常或错误而终止

  • 执行了System.exit()方法

  • 操作系统出现错误导致虚拟机终止

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰 羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值