类加载子系统之类加载机制与类加载器

一、JVM的生命周期

虚拟机的声明周期可以分为三个阶段:

  1. 虚拟机的启动

    虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现来指定的。

  2. 虚拟机的执行

    ​ 一个运行中的Java虚拟机有这一个清晰的任务就是执行Java程序,程序开始执行时虚拟机,当程序运行结束时,虚拟机就结束,在执行一个Java程序的时候,真真正正执行的是一个叫做Java虚拟机的进程。

  3. 虚拟机的退出

    虚拟机的退出又分为几种情况:

    • 程序正常执行结束
    • 程序运行过程中遇到异常而终止
    • 操作系统出现错误而导致Java虚拟机进程终止
    • 某个线程调用Runtime类或System类的exit方法,或者Runtime类的halt方法,并且Java安全管理器也允许这次exit或者halt操作。
    • 还有一种就是JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载Java虚拟机时,Java虚拟机退出的情况。

二、常见的Java虚拟机

  • Classic VM:,它是世界上第一款商用虚拟机。这款虚拟机只提供解释器并没有即时编译器。
  • Exact VM:sun提供的又一款虚拟机Exact Memory Management,准确式内存管理。
  • HotSpot VM:大多数的jdk的默认虚拟机,也是Oracle JDK和Open JDK的默认虚拟机,其拥有解释器和即时编译器,在1.8的jdk中HotSpot中整合了JRockit虚拟机的优秀特性。
  • JRockit:BEA的JRockit虚拟机,号称世界上最快的虚拟机,2008年BEA被Oracle收购。
  • J9:IBM公司的J9虚拟机,是目前最右影响力的三大商用服务器之一。2017年,IBM发布了开源J9VM命名为OpenJ9,交给了Ecilpse基金会,也称为Ecilpase OpenJ9。
  • TaobaoJVM:由AliJVM团队发布,是阿里巴巴基于OpenJDK开的自己的定制版本AlibabaJDk,它是国内第一个优化,深度定制且开源的高性能服务器。目前已经在淘宝天猫上线

三、虚拟机的类加载机制

3.1、概述

​ Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称为虚拟机的类加载机制,在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的。

3.2、类加载器的作用和角色

3.2.1 类加载器的作用

类加载器的作用就是将类class文件加载到内存中,对方法区中的类信息的访问都需要经过这个Class对象

image-20201006210716195

3.2.1类加载器所扮演的角色

image-20201006211339225

3.3、类加载过程

类的加载过程分为五个阶段

image-20201006212122660

3.3.1、加载过程

image-20201006212151731

image-20201006212211611

类加载后会在堆区生成一个唯一的Class对象。

3.3.2、链接过程

image-20201006212632790

在准备阶段如果变量是final static修饰的常量则不会赋予零值,而是会直接赋值如:以下代码会被直接赋值为123;

public static int value = 123

3.3.3、初始化过程

image-20201006212830197

类构造器是收集静态代码和静态变量形成的,如果没有静态变量和静态代码块则并不会有类构造器,初始化过程就是执行类构造器的过程。

注意:执行接口的类加载器并不需要先执行父接口的类构造器,因为只有当父接口中定义的变量被使用时,父接口才会被初始化,此外,接口的实现类在初始化时也一样不会执行接口的类构造器

3.4、类加载的时机

类的加载过程只有五个阶段但是类的生命周期会经历,加载,验证,准备,解析,初始化,使用和卸载七个阶段,其中验证,准备,解析统称为链接。

对类的主动使用会触发类的初始化

java程序对类的主动引用:

image-20201006214631495

添加一点:深入理解Java虚拟机第三版中提到当一个接口中定义了jdk8新加入的默认方法(被default关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那改接口要在其之前被初始化


对类的别动引用不会触发类的初始化

java程序对类的被动引用:

  • 通过子类引用父类的静态字段不会导致子类的初始化
  • 通过数组定义来引用类,不会触发此类的初始化
  • 常量在编译器会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

四、类加载器的分类

image-20201006220112319

  • 启动类加载器

image-20201006220422636

  • 扩展类加载器

image-20201006220443013

在jdk9中,扩展类加载器被平台类加载器所取代

  • 应用程序类加载器

image-20201006220457569

image-20201006220144771


用户自定义的加载器

为什么要自定义类加载器:

image-20201006220621262

如何自定义类加载器:

image-20201006220651161

自定义的类加载器需要继承ClassLoader,有关ClassLoader

image-20201006220748642

image-20201006220801918

如何获取类加载器:

image-20201006220833709

通过方式一获取类加载器,如果获取到的结果为null,则表示是一个引导类加载器

例如:

//	shareData是自定义类  ,String类是由引导类加载器加载的
public static void getClassLoader(){
    
    
        String s = new String();
        System.out.println("类加载器"+s.getClass().getClassLoader());
        ShareData shareData = new ShareData();
        System.out.println("类加载器"+shareData.getClass().getClassLoader());
    }

image-20201006222112848

注意:判断两个class对象是否是同一个类要注意两点:

  • 类的全限定类型是否相等
  • 加载类的类加载器是否相同

image-20201006221047149

image-20201006221059046

五、双亲委派模型

image-20201006221206457

image-20201006221218357

image-20201006221358244

image-20201006221411801

沙箱安全机制

image-20201006221432965
双亲委派机制可以保护核心类不被篡改:

自定义一个java.lang.String类,在main方法中会报错

package java.lang;

/**
 * @author mypc
 * @create 2020 上午 11:40
 */
public class String {
    
    
    //
    static{
    
    
        System.out.println("我是自定义的String类的静态代码块");
    }
    public static void main(String[] args) {
    
    
        System.out.println("hello,String");
    }
}

image-20201006225154286

即使这个类是自定义的,理应由应用程序类加载器加载,但是由于双亲委派机制的存在,它会首先将该类交给它的父类加载器扩展类加载器加载,扩展类加载器又会交给它的父类加载器引导类加载器加载,而java.lang.String这个类在jdk的核心类库中存在,所以它会直接加载核心类库中的java.lang.String,不会加载我们自定义的java.lang.String,这样就防止了核心API被随意串改。java的核心类java.lang.String没有main方法,所以报错.
以上内容是根据尚硅谷的视屏和深入理解Java虚拟机第三版整理的笔记,如有错误请指出

猜你喜欢

转载自blog.csdn.net/qq_44134480/article/details/108944888