JVM底层原理学习(二)之类加载子系统

上一篇 JVM底层原理学习(一)之JVM概述
上一篇文章主要分析了为什么JVM可以跨平台,以及一个java文件的编译与反编译的过程。这一篇主要是介绍JVM的类加载子系统

类文件到虚拟机(类加载子系统)

所谓类装载机制就是

虚拟机把Class文件加载到内存
并对数据进行校验,转换解析和初始化
形成可以虚拟机直接使用的Java类型,即java.lang.Class

在这里插入图片描述

加载(load)(加载到内存中)

查找和导入class文件

  • 通过一个类的全限定名获取定义此类的二进制字节流(从class,jar, 网络…)
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

链接(link)

验证

保证被加载类的正确性

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备

为类的静态变量分配内存,并将其初始化为默认值
在这里插入图片描述

解析

把类中的符号引用转换为直接引用

符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

将下图的符号引用转换为真正的地址
在这里插入图片描述

初始化(init)

为类的静态变量赋值,然后执行类的初始化(static)语句
初始化的详细过程:

  • 如果类还没有被加载和链接,那就先进行加载和链接
  • 如果类存在父类,并且父类还没有初始化,那就先初始化直接父类
  • 如果类中存在初始化语句,顺序执行初始化语句
类的初始化阶段是执行类构造器方法clinit()的过程
1、类加载就是执行Java程序编译之后在字节码文件中生成的clinit()方法(称之为类构造器),clinit()方法由静态变量和静态代码块组成。
2、子类的加载首先需要先加载父类,如果父类为接口。则不会调用父类的clinit方法。一个类中可以没有clinit方法。
3、clinit方法中的执行顺序为:父类静态变量初始化,父类静态代码块,子类静态变量初始化,子类静态代码块。
4、clinit()方法只执行一次。

类的初始化时机: 即在java代码中首次主动使用的时候, 包含以下情形:

  • (首次)创建某个类的新实例时–new, 反射, 克隆 或 反序列化;
    例:new xxxClass()|Class.newInstance()|constructor.newInstance());
  • (首次)访问类中的某个静态变量,或者对静态变量进行赋值
  • (首次)主动调用类的静态方法
  • (首次)调用java的某些反射方法时,例如:Class.forName(“abc”);
  • (首次)完成子类初始化,也会完成对本类的初始化(接口例外)
  • (首次)该类程序引导入口(mian入口或者test入口)

注:clinit是类初始化方法,init是对象初始化方法
有一篇文章讲的不错,可以了解下:https://www.jianshu.com/p/8a14ed0ed1e9
面试题

  • Class.forName(“abc”);
  • Clazz.class.getClassLoader().loadClass(“abc”);
    这两种加载类的区别:
    经过我的测试如下
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
可以看到,Class.forName(“com.xhc.test.Son”);会调用类初始化方法clinit();而Clazz.class.getClassLoader().loadClass(“com.xhc.test.Son”)则不会。

类加载器ClassLoader

整个类的装载流程及各个步骤中主要工作内容,我们已经有了了解. 同时也进行了对应的验证.接下来我们是不是自然而言想到,这个加载的流程到底是谁来完成上诉的步骤呢? (load-link-initialize),完成上诉工作的组件就叫类加载器(ClassLoader)
下面一起了解一下JDK中自带的类加载器

Bootstrap ClassLoader (引导加载器)

  • JVM自带的引导类加载器.由C/C++的语言实现,在java中打印null.
  • 加载java的核心类库、$JAVA_HOME中jre/lib/rt.jar、resouce.jar(或 java程序运行指定的Xbootclasspath选项jar包)
  • 只能加载java,javax,sun等开头的包名类.

Extension ClassLoader (扩展加载器)

  • Java语言编写的类加载器 sun.misc.Launcher$ExtClassLoader
  • 它的父类为Bootstrap Classloader,getParent()可以获取BootstrapClassloader
  • 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME 中jre/lib/ext或
    -Djava.ext.dirs指定目录下的jar包(如果我们自定义的class需要交给Ext来加载可以放置到ext的目录下)

Application ClassLoader (应用加载器)

  • Java语言编写的类加载器(sun.misc.Launcher$AppClassLoader)
  • 该类是程序中默认的类加载器,java应用的类都是该类加载器加载的.
  • Extension ClassLoader为它的父类,getParent()可以获取ExtensionClassLoader
  • 负责加载环境变量classpath指定的目录,或者java.class.path指定的目录类库

双亲委派机制

  1. 检查某个类是否已经加载

自底向上,从Application ClassLoader到BootStrap ClassLoader逐层检查,
只要某个Classloader已加载,就视为已加载此类,保证此类只被ClassLoader加载一次。

  1. 加载的顺序,向上委托

自顶向下,也就是由上层来逐层尝试加载此类。
双亲委派机制的实现代码,如下图所示,是不是很短啊~~~~,原来以为很牛逼的东西,其实懂了的话,就是一个if else 啊啊
在这里插入图片描述
在这里插入图片描述

为什么选择双亲委派机制

  • 避免类的重复加载
  • 保护程序安全,防止核心的JAVA语言环境遭受破坏.
    如果我们自定义一个java.lang.String的类.定义static语句,并不会在new String()的过程中打印static语句打印的内容

自定义ClassLoader

  • 为什么需要自定义ClassLoader?
    适配各种class源
    • 从数据库,缓存,网络,一切可以存储class的存储介质
    • 修改类的加载方法
      比如:tomcat中多个war工程都可以独立的运行.在各个war包中的jar包并不会冲突…
    • 防止源码泄露 对class字节码文件进行编码加密的方式.在load过程中对其
      进行解密操作.
  • 如何自定义ClassLoader
    extends ClassLoader 重写loadClass方法extends UrlClassLoader

附:Tomcat的类的加载器

在这里插入图片描述
下一篇 JVM底层原理学习(三)之JVM内存区域

猜你喜欢

转载自blog.csdn.net/nonage_bread/article/details/108028459