首页 > 其他分享 >JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制

时间:2022-12-30 18:36:14浏览次数:42  
标签:委派 jdk1.8 Java lib Program 双亲 class 加载


文章目录

  • ​​前言​​
  • ​​一、类初始化之 类加载​​
  • ​​1、总述​​
  • ​​2、类加载器​​
  • ​​3、类加载器层次​​
  • ​​4、网络上错误的继承关系​​
  • ​​二、类加载器 详情​​
  • ​​1、加载过程之双亲委派机制​​
  • ​​a、双亲委派机制​​
  • ​​b、类加载过程​​
  • ​​2、父加载器​​
  • ​​3、类加载器范围​​
  • ​​a、三个类加载器:​​
  • ​​b、 `sun.misc.Launcher$ExtClassLoader@1b6d3586` 解析如下​​
  • ​​c、 Launcher类源码如下​​
  • ​​d、三类加载器加载的包位置​​
  • ​​4、自定义类加载器​​
  • ​​a、三步​​
  • ​​b、案例:加载其他类​​
  • ​​c、读loadClass源码​​
  • ​​d、自定义类加载器​​
  • ​​e、加载流程​​
  • ​​5、加密​​
  • ​​6、编译器​​
  • ​​a、编译器描述​​
  • ​​b、小案例​​
  • ​​7、JVM的懒加载模式​​
  • ​​a、懒加载​​
  • ​​b、 案例代码​​
  • ​​三、本章总结​​
  • ​​1、加载过程​​
  • ​​2、小总结:​​
  • ​​四、打破双亲委派机制-热部署应用​​
  • ​​1、双亲委派机制案例​​
  • ​​a、自定义类加载器:T006_MSBClassLoader​​
  • ​​b、双亲委派机制代码​​
  • ​​2、打破双亲委派机制​​

前言

  1. 切换jdk版本,我目前用的是最高版本18,但是学习需要改成8版本:​​JDK不同版本切换​​
  2. 本博文主要讲解:类初始化过程中的 ==类加载的过程细节,也就是 ClassLoader ==

一、类初始化之 类加载

1、总述

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_java


编译好的class文件默默的趟在了硬盘上,怎样才可以到内存里并准备好呢,如图三大步所示,解析如下:

  1. ​loading​​​:把class文件 load 到 ​​内存​
  2. ​linking​​:
  1. ​verification​​:校验,加载的class 是否符合class文件的标准,比如,一个文件的开头不是CAFEBABE,说明不是class文件,则校验失败。
  2. ​preparation​​:很重要,把静态变量赋默认值比如:如果类中有 i=8,在这里会给i赋值0,并不会在这儿赋值8
  3. ​resolution​​:class文件里常量池里面用的到 符号引用 转换成 访问到的内存地址的内容
    将类、方法、属性等 ​​​符号引用​​​ 解析为 ​​直接引用​​​。常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
    源码的体现如下:
  1. ​initializing​​:调用静态代码块为静态变量赋值为初始值

2、类加载器

  • JVM本身有一个类加载器的层次,类加载器就是一个普通的class,分别加载不同的class类。
  • JVM所有的class都是被类加载器加载到内存的,​​类加载器​​ 简单称为ClassLoader。
  • 不同的类被不同的类加载器加载到内存。
  • 下图中,​CustomClassLoader的父加载器是 Application,其父加载器是 Extension 加载器,其父加载器是Bootstrap 加载器,这里指的是父类加载器,并无继承关系。

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jvm_02

  • 有一个​​顶级的抽象父类​​如下:
  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_03

  • 如何看类是被哪个ClassLoader加载到内存的呢​,很简单,看下面代码。一个类被扔到内存之后会有两块内容,第一块内容是把二进制的内容放到内存里;第二块内容与之同时生成了class类的对象,指向了第一块内容。以后我们写的对象去访问第二块内容生成的对象,然后在引用第一块内容。第一块内容是在metespace(元空间)中。
package com.mashibing.jvm.c2_classloader;

public class T002_ClassLoaderLevel {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
String i = "sd";
// 第二种方式
System.out.println(i.getClass().getClassLoader());
System.out.println(sun.awt.HKSCS.class.getClassLoader());
// ext 下的,但是目前我电脑上没有。
// System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());

// 自己写的类,是由Application 加载的
System.out.println(T002_ClassLoaderLevel.class.getClassLoader());
System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());
System.out.println(new T006_MSBClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader());
}
}
  • 下图可见,有两个,一个null,一个 AppClassLoader。接下来就要说类加载器的层次啦
  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_04

3、类加载器层次

类加载器是分成不同的层次来加载的,不同的类加载器加载不同的class类(上面也说过啦)

  1. 最顶层的叫​​Bootstrap​​,最开始的类加载器,负责加载JDK里面最核心的那些jar文件里包类,又c++实现的类加载器,通过ClassLoader 拿到类加载器的结果是null的时候,就是最顶级类加载器
  2. 其次是 ​​Extension​​​,​​ext​​扩展类加载器,负责加载扩展包里的类文件,在java安装路径中 有个ext 文件,就是扩展包。
  3. 然后就是​​Appcliaction​​,加载classpath 指定内容,我们写的类默认在这个路径里。
  4. 最后就是自定义类加载器:​​CustomClassLoader​​。自定义的ClassLoader。
  • 加载过程是双亲委派机制 (下一节讲)

4、网络上错误的继承关系

下图在很多地方有出现,这是错误的,并无继承关系,只是在语法上有基础之意。

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_05

二、类加载器 详情

1、加载过程之双亲委派机制

class 文件编译后,需要加载到内存,如何加载呢,就是双亲委派机制来进行加载。

a、双亲委派机制

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_Java_06


JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_07

结合上图,
​​如果有个类,要加载到内存中,先从最下边的左边

  1. 的CustomClassLoader 去加载,CustomClassLoader 看缓存里是否加载了,如果没有;
  2. 问父加载器 ApplicationClassLoader 有没有加载进来,ApplicationClassLoader 看缓存里是否加载了,如果没有;
  3. 问父加载器 ExtensionClassLoader 有没有加载进来,ExtensionClassLoader 看缓存里是否加载了,如果没有;
  4. 问父加载器 BootstrapClassLoader 有没有加载进来,BootstrapClassLoader 看缓存里是否加载了,如果没有。
  5. 则从右边回过头来往下走看是否是 BootstrapClassLoader 应该加载的类,如果不是;
  6. 则往下来看是否是ExtensionClassLoader 应该加载的类,如果不是;
  7. 则往下来看是否是ApplicationClassLoader 应该加载的类,如果不是;
  8. 则往下来看是否是 CustomCLassLoader 应该加载的类,如果是则加载进来
  9. 如果还不是,则报错:​​ClassNotFoundException...​
  10. 这就是双亲委派进制
  11. 对应的代码是 ClassLoader.loadClass(),通过递归来实现的。
  12. JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_08


b、类加载过程

也就是双亲委派机制,如下图,更加清晰明了。

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_java_09

  • 这里的缓存是:自己的类加载器所维护的容器,一个数组。将加载的类文件都放里面了。

  • 双亲:是指从​​子加载器​​ 到 ​​父加载器​​ ,再从 ​​父加载器​​ 到 ​​子加载器​​。不要再上面加类,这里和类是没有关系的。(这里的翻译是比较怪的)

  • 面试题必问之一:为什么要搞双亲委派机制,这么麻烦,直接放到容器不行吗?

    答:因为安全,假如用反证法,假如给你任何一个class,你​​自定义的class​​,都可以自由的把它 load 到内存,那么给这么一个类:​​java.lang.String​​,自定义的类,直接将oracle内部写的类覆盖掉。按说不应该,需要看其他ClassLoader 是否加载了这个类,如果加载了就不用下载了。

2、父加载器

  • 父加载器

    • 父加载器不是“类加载器的加载器”!!!!!也不是“类加载器的父类加载器”
  • 双亲委派是一个​​孩子向父亲方向,然后父亲向孩子方向的双亲委 派过程​

  • 思考:为什么要搞双亲委派

    • – java.lang.String类由自定义类加载器加载行不行?(​2.2中给出了问答​)
  • 后来 8版本以后 ExtClassLoader 变成了 ​PlatformClassLoader​

package com.mashibing.jvm.c2_classloader;

public class T004_ParentAndChild {
public static void main(String[] args) {
//AppClassLoader
System.out.println(T004_ParentAndChild.class.getClassLoader());
// AppClassLoader 的class 的 classLoader 是null
System.out.println(T004_ParentAndChild.class.getClassLoader().getClass().getClassLoader());
// AppClassLoader 的父加载器 是 PlatformClassLoader (原来是Ext)
System.out.println(T004_ParentAndChild.class.getClassLoader().getParent());
// null,类似于上面第二个
System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent());
// 空指针报错
//System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent().getParent());
}
}

java18版本

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_java_10


8版本如下图所示

​ExtensionClassLoader​​ 和 ​​PlatformCLassLoader​​ 是等价的,8版本之后

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_Java_11

​Launcher​​ 类所在的包是​​sun.misc​​。

3、类加载器范围

a、三个类加载器:

1. Bootstrap 
2. ExtensionClassLoader
3. AppClassLoader

b、 ​​sun.misc.Launcher$ExtClassLoader@1b6d3586​​ 解析如下

三个类加载器都在 ​​Launcher​​ 类中,为其​​内部类​​,​​$ 符号​​ 后面的是加载器名称,也是内部类类名,​​@符号​​ 后面的该类具体的 ​​哈希code码​​。

c、 Launcher类源码如下

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_java_12


(来自Launcher源码)

  • sun.boot.class.path

    • Bootstrap ClassLoader加载器的 加载路径
    • 截图如下:
    • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jvm_13


  • java.ext.dirs

    • ExtensionClassLoader加载器的 加载路径
    • 截图如下:
    • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_java_14


  • java.class.path

    • AppClassLoader加载器的 加载路径
    • 截图如下:
    • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_15


d、三类加载器加载的包位置

找到对应的包,然后用换行符替换分号,便于可视化观看。

package com.mashibing.jvm.c2_classloader;

public class T003_ClassLoaderScope {
public static void main(String[] args) {
String pathBoot = System.getProperty("sun.boot.class.path");
System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));

System.out.println("--------------------");
String pathExt = System.getProperty("java.ext.dirs");
System.out.println(pathExt.replaceAll(";", System.lineSeparator()));

System.out.println("--------------------");
String pathApp = System.getProperty("java.class.path");
System.out.println(pathApp.replaceAll(";", System.lineSeparator()));
}
}

打印如下

"C:\Program Files\Java\jdk1.8.0_351\bin\java.exe" "-javaagent:D:\devApp\idea\IntelliJ IDEA Educational Edition 2022.1\lib\idea_rt.jar=52418:D:\devApp\idea\IntelliJ IDEA Educational Edition 2022.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_351\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_351\jre\lib\rt.jar;E:\msb\学习路线\chapter03_JVM\(剪) JVM调优第一版\out\production\JVM" com.mashibing.jvm.c2_classloader.T003_ClassLoaderScope
C:\Program Files\Java\jdk1.8.0_351\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_351\jre\classes
--------------------
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
--------------------
C:\Program Files\Java\jdk1.8.0_351\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\deploy.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\access-bridge-64.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\cldrdata.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\dnsns.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\jaccess.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\jfxrt.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\localedata.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\nashorn.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunec.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunjce_provider.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunmscapi.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\sunpkcs11.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\ext\zipfs.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\javaws.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jfxswt.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\management-agent.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\plugin.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_351\jre\lib\rt.jar
E:\msb\学习路线\chapter03_JVM\(剪) JVM调优第一版\out\production\JVM
D:\devApp\idea\IntelliJ IDEA Educational Edition 2022.1\lib\idea_rt.jar

进程已结束,退出代码0

这里有个重点:

这里是当前项目的路径,也会从这里加载,所以我们写的代码也属于APPClassLoader 类加载器啦。

最下面那个就是idea的路径啦。

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_java_16

4、自定义类加载器

a、三步

  1. 继承 ​​ClassLoader​​ 类
  2. 重写模板方法 ​​findClass​
    • 调用 ​​defineClass​
  3. 自定义类加载器加载自加密的class
    • 防止反编译
    • 防止篡改

b、案例:加载其他类

package com.mashibing.jvm.c2_classloader;

public class T005_LoadClassByHand {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = T005_LoadClassByHand.class.getClassLoader().loadClass("com.mashibing.jvm.c2_classloader.T002_ClassLoaderLevel");
System.out.println(clazz.getName());

//利用类加载器加载资源,参考坦克图片的加载
//T005_LoadClassByHand.class.getClassLoader().getResourceAsStream("");
}
}

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_17

  1. ​T005_LoadClassByHand.class.getClassLoader()​​ 获取的是APPClassLoader 类加载器,
  2. 通过 ​​loadClass​​ 方法进行加载指定的 ​​T002_ClassLoaderLevel​​类。然后将此类加载进来。
  3. 将此类加载 到内存,会返回class 对象:​​com.mashibing.jvm.c2_classloader.T002_ClassLoaderLevel​
  • 题外话:loadClass() 是反射的基石。
  • 什么时候去加载一个类呢?
    spring 有一个动态代理,会自动加载。

c、读loadClass源码

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_Java_18

  1. 先加锁 ​​synchronized​​,以防加载过程中,其他类在加载
  2. 首先 ​​findLoadedClass​​,先查看是否加载过了,如果没有加载过,在进行加载。底边源码直接到native了
  3. 父加载器再进行查找,看是否加载了,进入​​递归模式委派机制​​,
  4. 如果没有找到在回来进行 加载类 ​​findClass​​。
  5. 所以,如果自定义类加载器,则重写 ​​findClass​​方法即可。

d、自定义类加载器

d盘下的Hello.class

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_19

package com.mashibing.jvm;

public class Hello {
public Hello() {
}

public void m() {
System.out.println("Hello JVM!");
}
}

自定义的 ClassLoader

package com.mashibing.jvm.c2_classloader;

import com.mashibing.jvm.Hello;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class T006_MSBClassLoader extends ClassLoader {

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("d:/test/", name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;

while ((b=fis.read()) !=0) {
baos.write(b);
}

byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨

return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}

public static void main(String[] args) throws Exception {
ClassLoader l = new T006_MSBClassLoader();
Class clazz = l.loadClass("com.mashibing.jvm.Hello");
Class clazz1 = l.loadClass("com.mashibing.jvm.Hello");

System.out.println(clazz == clazz1);

Hello h = (Hello)clazz.newInstance();
h.m();

System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());

System.out.println(getSystemClassLoader());
}
}

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_20

e、加载流程

  1. ​从底层加载器到顶级加载器 检查 对应缓存看是否加载过​​。先进入到自定义类加载器看是否有没有被加载,若没有,则到父加载器APPClassLoader ,若APPClassLoader也没有加载过,则到其父加载器ExtCLassLoader,若ExtClassLoader也没有加载过,则到顶级父类加载器 BootstrapClassLoader ,若也没有加载。则进行下面的加载逻辑
  2. ​再从顶级加载器到底层加载器开始 加载 该类​​。该类BootstrapClassLoader 加载器 看到该类(​​D:\test\com\mashibing\jvm\Hello.class​​)不属于自己的加载路径,则交给ExtClassLoader加载器,ExtClassLoader 发现也不属于自己的加载路径,则交给APPClassLoader加载器,但APPClassLoader 加载器发现也不属于自己的加载路径,则交给自定义加载路径。然后加载。
  3. 然后从​​自定义的 findClass()​​去定义class并进行返回。

5、加密

加密,进行了异或操作。

package com.mashibing.jvm.c2_classloader;

import com.mashibing.jvm.Hello;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class T007_MSBClassLoaderWithEncription extends ClassLoader {

public static int seed = 0B10110110;

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("c:/test/", name.replace('.', '/').concat(".msbclass"));

try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;

while ((b=fis.read()) !=0) {
baos.write(b ^ seed);
}

byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨

return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}

public static void main(String[] args) throws Exception {

encFile("com.mashibing.jvm.hello");

ClassLoader l = new T007_MSBClassLoaderWithEncription();
Class clazz = l.loadClass("com.mashibing.jvm.Hello");
Hello h = (Hello)clazz.newInstance();
h.m();

System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());
}

private static void encFile(String name) throws Exception {
File f = new File("c:/test/", name.replace('.', '/').concat(".class"));
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(new File("c:/test/", name.replaceAll(".", "/").concat(".msbclass")));
int b = 0;

while((b = fis.read()) != -1) {
fos.write(b ^ seed);
}

fis.close();
fos.close();
}
}

6、编译器

a、编译器描述

java 是解释执行的,class文件load到内存,可以通过java解释器interpret 解释执行。

java有一个 JIT(just in time)编译器,有某些代码需要编译成为本地代码,相当于exe,java代码也可以编译成为本地代码来执行。

所以java是即可以解释也可以编译。默认是混合模式。

  • 解释器
    • bytecode intepreter
  • JIT
    • Just In-Time compiler
  • 混合模式
    • 混合使用解释器 + 热点代码编译 (代码先解释执行,发现有一段代码执行了上万次,则编译成本地代码执行)
    • 起始阶段采用解释执行
    • 热点代码检测 (如果确定热点代码呢?
      • 多次被调用的方法(方法计数器:监测方法执行频率)
      • 多次被调用的循环(循环计数器:检测循环执行频率)
      • 进行编译

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_21

  • -Xmixed 默认为 ​混合模式

    • 开始解释执行,启动速度较快 对热点代码实行检测和编译
  • -Xint 使用解释模式

    • 启动很快 执行稍慢
  • -Xcomp 使用纯编译模式

    • 执行 很快,启动很慢
  • exe 是 Windows 的本地代码格式,Linux 的本地代码格式是elf。

b、小案例

package com.mashibing.jvm.c2_classloader;

public class T009_WayToRun {
public static void main(String[] args) {
for(int i=0; i<10_0000; i++)
m();

long start = System.currentTimeMillis();
for(int i=0; i<10_0000; i++) {
m();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

public static void m() {
for(long i=0; i<10_0000L; i++) {
long j = i%3;
}
}
}
  • 代码中m()方法重复执行了很多遍。所以会是编译执行
  • 其中的​​long start = System.currentTimeMillis();​​会是解释执行
  • 下图是该类运行的配置,红框中是没有参数的,说明是 ​默认的混合模式(解释+编译)​,执行大约是4秒左右。
  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_Java_22


  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_23


  • 然后修改配置为解释编译如下,​​-Xint​​ 是纯解释型。没有内部编译的过程。大约4.5左右。
  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_24


  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_Java_25


  • 改成 编译模式,​​-Xcomp​​ 大约4秒,显示区别不大,但是类特别多的情况下,区别还是很大的。
  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_26


  • JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_27


7、JVM的懒加载模式

a、懒加载

  1. 严格讲应该叫 lazyInitializing。

  2. JVM 规范并没有规定何时加载。

  3. JVM 严格规定了什么时候必须初始化。(五种情况进行懒加载

    • ​new getstatic putstatic invokestatic​​ 指令,访问final变量除外
    • ​java.lang.reflect​​ 对类进行反射调用时
    • 初始化子类的时候,父类首先初始化
    • 虚拟机启动时,被执行的主类必须初始化
    • 动态语言支持 ​​java.lang.invoke.MethodHandle​​ 解析的结果为
      • REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初 始化
  4. 按需加载

b、 案例代码

package com.mashibing.jvm.c2_classloader;

/**
* 严格讲应该叫 lazy initialzing,
* 因为java虚拟机规范并没有严格规定什么时候必须 loading,但严格规定了什么时候 initialzing
*/
public class T008_LazyLoading {
public static void main(String[] args) throws Exception {
P p;
X x = new X();
System.out.println(P.i);
System.out.println(P.j);
Class.forName("com.mashibing.jvm.c2_classloader.T008_LazyLoading$P");

}

public static class P {
final static int i = 8;
static int j = 9;
static {
System.out.println("P");
}
}

public static class X extends P {
static {
System.out.println("X");
}
}
}

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_Java_28

三、本章总结

1、加载过程

  • Loading

    1. 双亲委派,主要出于安全来考虑
    2. LazyLoading 五种情况
      • –new getstatic putstatic invokestatic指令,访问final变量除外
      • –java.lang.reflect对类进行反射调用时
      • –初始化子类的时候,父类首先初始化
      • –虚拟机启动时,被执行的主类必须初始化
      • –动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
    3. ClassLoader的源码
      • findInCache -> parent.loadClass -> findClass()
    4. 自定义类加载器
      1. extends ClassLoader
      2. overwrite findClass() -> defineClass(byte[] -> Class clazz)
      3. 加密
      4. 第一节课遗留问题:parent是如何指定的,打破双亲委派,学生问题桌面图片
        1. 用super(parent)指定
        2. 双亲委派的打破
          1. 如何打破:重写loadClass()
          2. 何时打破过?
            1. JDK1.2之前,自定义ClassLoader都必须重写loadClass()
            2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
            3. 热启动,热部署
              1. osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
    5. 混合执行 编译执行 解释执行

    1. 检测热点代码:-XX:CompileThreshold = 10000
    1. Linking

      1. Verification
        1. 验证文件是否符合JVM规定
      2. Preparation
        1. 静态成员变量赋默认值
      3. Resolution
        1. 将类、方法、属性等符号引用解析为直接引用
          常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
    2. Initializing

      1. 调用类初始化代码 ,给静态成员变量赋初始值

2、小总结:

  1. load - 默认值 - 初始值
  2. new - 申请内存 - 默认值 - 初始值
四、打破双亲委派机制-热部署应用

1、双亲委派机制案例

a、自定义类加载器:T006_MSBClassLoader

package com.mashibing.jvm.c2_classloader;

import com.mashibing.jvm.Hello;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class T006_MSBClassLoader extends ClassLoader {

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("d:/test/", name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;

while ((b=fis.read()) !=0) {
baos.write(b);
}

byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨

return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}

public static void main(String[] args) throws Exception {
ClassLoader l = new T006_MSBClassLoader();
Class clazz = l.loadClass("com.mashibing.jvm.Hello");
Class clazz1 = l.loadClass("com.mashibing.jvm.Hello");

System.out.println(clazz == clazz1);

Hello h = (Hello)clazz.newInstance();
h.m();

System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());

System.out.println(getSystemClassLoader());
}
}

b、双亲委派机制代码

package com.mashibing.jvm.c2_classloader;

public class T011_ClassReloading1 {
public static void main(String[] args) throws Exception {
T006_MSBClassLoader msbClassLoader = new T006_MSBClassLoader();
Class clazz = msbClassLoader.loadClass("com.mashibing.jvm.Hello");

msbClassLoader = null;
System.out.println(clazz.hashCode());

msbClassLoader = null;

msbClassLoader = new T006_MSBClassLoader();
Class clazz1 = msbClassLoader.loadClass("com.mashibing.jvm.Hello");
System.out.println(clazz1.hashCode());

System.out.println(clazz == clazz1);
}
}

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_jar_29


解析:

因为自定义类加载器时,重写的是 ​​findClass()​​ 方法,所以执行的还是双亲委派机制。所以第二次 ​​msbClassLoader = new T006_MSBClassLoader();​​的时候,先进行查找是否加载过,加载过的话就找到直接返回。

因为第一次new加载过了,所以第二次new就不用加载了,找到直接返回即可。

所以打印的两个hashcode值是一样的,而且返回TRUE。

2、打破双亲委派机制

package com.mashibing.jvm.c2_classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class T012_ClassReloading2 {
private static class MyLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {

File f = new File("d:/test/" + name.replace(".", "/").concat(".class"));

if(!f.exists()) return super.loadClass(name);

try {

InputStream is = new FileInputStream(f);

byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}

return super.loadClass(name);
}
}

public static void main(String[] args) throws Exception {
MyLoader m = new MyLoader();
Class clazz = m.loadClass("com.mashibing.jvm.Hello");

m = new MyLoader();
Class clazzNew = m.loadClass("com.mashibing.jvm.Hello");

System.out.println(clazz == clazzNew);
}
}

JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制_学习_30

解析:
首先自定义类加载器时,这里重写的是 ​​loadClass​​,所以就不会走源代码写死的双亲委派机制。

所以这里加载的逻辑是:如果类文件不存在则交给父类加载器去加载,但是一旦类文件存在,则直接加载到内存。所以就会出现,只要是自己的类文件则一定会加载。

所以这里返回的FALSE,因为 两次new 都是重新加载到内存的

所以热部署的应用也是依靠这个打破双亲委派机制而进行改造的。




标签:委派,jdk1.8,Java,lib,Program,双亲,class,加载
From: https://blog.51cto.com/u_15926676/5981140

相关文章