JVM之类装载子系统

    技术2022-07-13  54

    返回主博客

    目录

    概述

    类加载过程

    类加载器

    双亲委派机制


    概述

       类加载子系统负责将我们的class文件加载到JVM的内存模型中,在方法区加入他的类信息,在堆中创建对应的java.lang.Class对象。这个类就可以被java程序所使用了。

    类加载过程

    分为 加载->链接->初始化 三个阶段

     

    加载

    通过类的全限定名,从文件系统(或者网络等其他方式)加载Class文件,将这些字节流的文件信息,转化为运行时的数据结构,生成java.lang.Class对象。作为方法区这个类的各种数据的访问入口。

    加载的类信息放置在方法区,除了类信息,方法区还会存放运行时常量池(其实就是Class文件的常量池)信息,可能包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池映射)

     

    链接:

    验证-> 准备 -> 解析 三个阶段

    验证(verify)

    字节码合法性:格式,元数据,字节码,引用符号。

    准备(prepare)

    为类变量分配内存以及为静态变量的赋默认初始值(即0值) 不包含final + static修饰的,final修饰的在编译阶段就已经赋值,所以准备阶段会显示初始化,而不是赋默认值。不会为实例变量分配初始化。

    解析(Resolve)

    将常量池引用(字面量形式)创建为直接引用

    主要针对常量池中的Constant_Class_info,Constant_Fieldref_info,Constant_methodref_info,对接口,类方法,接口方法,方法类型等,对进行解析,将class文件中的这些字面量信息生成直接引用。

    invokevirtual的处理。生成虚方法表,便于函数重写时知道调用哪个类的方法。

    事实上解析往往伴随实例化完成的。

     

    初始化:

    执行clinit (就是对类的static区的代码运行)

    虚拟机必须保证同一个类的clinit是被加锁的。

     

    补充:类和对象的创建和赋值顺序

    1、父类和子类的 final static 的基本数据类型赋值(准备阶段) ->

        父类static执行或static{}(谁在按顺序执行) -> 执行 子类static执行

    2、子类对象开始构造 -> 父类对象开始构造 -> 父类对象属性赋值 -> 子类对象属性赋值。

     

    类加载器

    主要有

    启动类加载器(bootstrap ClassLoader),扩展类加载器(extClassLoader),应用类加载器(SystemClassLoader/AppClassLoader)

    他们并非继承关系,这里说的parent 是 子类加载器会有一个成员变量叫parent的。比如AppClassLoader 的 parent属性中放的是ExtClassLoader。

     

    分为两类:

    引导类加载器(由C和C++编写的)

    自定义类加载器(所有派生于ClassLoader的都是自定义加载器,比如extClassLoader,AppClassLoader)

    代码案例:拿到ClassLoader.getSystemClassLoader(); 并打印他们的parent

    ClassLoader classLoader = ClassLoader.getSystemClassLoader(); System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2 System.out.println(classLoader.getParent()); //sun.misc.Launcher$ExtClassLoader@85ede7b System.out.println(classLoader.getParent().getParent()); //null System.out.println(ClassLoaderTest.class.getClassLoader()); //sun.misc.Launcher$AppClassLoader@18b4aac2 System.out.println(String.class.getClassLoader()); //null System.out.println(Integer.class.getClassLoader()); //null System.out.println(ArrayList.class.getClassLoader()); //null

    BootStrapClassLoader是获取不到的,并且是C和C++编写的,因此运行结果为null,他只负责加载java核心类库中的类,如String,Integer,ArrayList等。

     

    引导类加载器(BootStrapClassLoader /启动类加载器)

    C和C++编写

    启动java核心类库:jdk的,sun的,resource.jar的

    不继承ClassLoader,无父类加载器(肯定的不是java语言编写的)

    加载扩展类和应用类加载器,并指定为他们的父类加载器

    出于安全BootStrapClassLoader只加载java,javax,sun开头的(并且这些类即时被我们自定义了,JVM也只会加载jdk中的)

     

    虚拟机自带类加载器(ExtClassLoader)

    在cun.misc.Launcher实现,从java.ext.dir目录中或jdk安装目录jre/lib/ext目录下加载类,如果用户自定义类在此目录下,也会由自定义类加载器加载。

    虚拟机自带类加载器(AppClassLoader)

    在cun.misc.Launcher实现,负责环境变量或者java.class.path下的类库,是系统中默认的类加载器。

     可以由ClassLoader.getSystemClassLoader()获取

     

    用户自定义类加载器

    基本使用上面三种类加载器就够了,但是有些场景是需要的:

    1、隔离加载类: 某些框架当中需要使用中间件,中间件和应用模块隔离的,需要将类加载到不同环境当中,确保应用jar包和中间件的不冲突。比如中间件有自己的jar,但是可能会有类的冲突。很多主流框架都会自定义类加载器。

    2、修改类的加载方式。

    3、扩展加载源,可能考虑从其他来源加载类

    4、防止源码泄露,可以对字节码加密。解密的时候可以用自定义类加载器。

     

    如何自定义类加载器?

    继承ClassLoader或者URLClassLoader

    1.2之前可以重写loadClass,之后建议findClass配合defineClass实现。

     

    关于ClassLoader

    主要接口

    获取ClassLoader的方式

    clazz.getClassLoader() /Class.forName("aaa/bbb.XXX").geClassLoader;

    Thread.currentThread().getContextClassLoader()

    ClassLoader.getSystemClassLoader()

    DriverManager.getCallderClassLoader()

     

    双亲委派机制

    按需加载,需要用到才加载,是一种任务委派模式:

    如果一个类收到类加载请求,他不会立即加载,而是交给其父类加载器,如果父类加载器还存在父类加载器,则进一步委派,依次递归。一直到引导类加载器。如果父类加载器可以完成类加载则加载返回,否则交给子类。

    例子1

    如果我们自定义在java.lang下建一个String类,还是会加载核心类库的String防止恶意攻击,因为委托到BootStrapClassLoader的时候会识别到其为java.lang下面的。如果在这个自定义的类下面运行main方法,便找不到这个方法,因为加载的是核心类库下面的String

    例子2、

    我们实现rt.jar下面的接口,那么接口由BootStrapClassLoader加载,而实现类由AppClassLoader加载。

     

    双亲委派优势

    避免重复加载(比如有继承时)防止恶意攻击

    体现了沙箱安全机制 :将对核心类库的保护,成为沙箱安全机制

     

    补充

    判断java中两个Class对象是不是同一个类的条件:

    类全限类名一样ClassLoader是一样的

    JVM必须知道一个类是由启动类加载器加载的还是启动类加载加载的,如果一个类是用户类加载器加载的,JVM会将类加载器的引用作为类的一部分信息保存在方法区当中,当解析一个类型到另一个类型引用的时候,JVM则必须保证两个类加载器是相同的。(动态链接)

     

    JVM中对类的使用分为主动和被动:(主要看有没有调用其clinit方法)

    主动创建方式:

    创建一个类的实例访问其静态变量访问其静态方法反射初始化其子类表明为启动类的

    1.7提供的动态语言。

    Processed: 0.045, SQL: 9