前言
- 切换jdk版本,我目前用的是最高版本18,但是学习需要改成8版本:JDK不同版本切换
- 本博文主要讲解:类初始化过程中的 ==类加载的过程细节,也就是 ClassLoader ==
一、类初始化之 类加载
1、总述
编译好的class文件默默的趟在了硬盘上,怎样才可以到内存里并准备好呢, 如图三大步所示,解析如下:
loading
:把class文件 load 到内存
linking
:verification
:校验,加载的class 是否符合class文件的标准,比如,一个文件的开头不是CAFEBABE,说明不是class文件,则校验失败。preparation
:很重要,把静态变量赋默认值。比如:如果类中有 i=8,在这里会给i赋值0,并不会在这儿赋值8resolution
:class文件里常量池里面用的到 符号引用 转换成 访问到的内存地址的内容
将类、方法、属性等符号引用
解析为直接引用
。常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
源码的体现如下:
initializing
:调用静态代码块为静态变量赋值为初始值。
2、类加载器
JVM本身有一个类加载器的层次,类加载器就是一个普通的class,分别加载不同的class类。
JVM所有的class都是被类加载器加载到内存的,
类加载器
简单称为ClassLoader。不同的类被不同的类加载器加载到内存。
下图中,
CustomClassLoader的父加载器是 Application,其父加载器是 Extension 加载器,其父加载器是Bootstrap 加载器,这里指的是父类加载器,并无继承关系。
有一个
顶级的抽象父类
如下:如何看类是被哪个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。接下来就要说类加载器的层次啦
3、类加载器层次
类加载器是分成不同的层次来加载的,不同的类加载器加载不同的class类(上面也说过啦)
- 最顶层的叫
Bootstrap
,最开始的类加载器,负责加载JDK里面最核心的那些jar文件里包类,又c++实现的类加载器,通过ClassLoader 拿到类加载器的结果是null的时候,就是最顶级类加载器 - 其次是
Extension
,ext
扩展类加载器,负责加载扩展包里的类文件,在java安装路径中 有个ext 文件,就是扩展包。 - 然后就是
Appcliaction
,加载classpath 指定内容,我们写的类默认在这个路径里。 最后就是自定义类加载器:
CustomClassLoader
。自定义的ClassLoader。- 加载过程是
双亲委派
机制 (下一节讲)
- 加载过程是
4、网络上错误的继承关系
下图在很多地方有出现,这是错误的,并无继承关系,只是在语法上有基础之意。
二、类加载器 详情
1、加载过程之双亲委派机制
class 文件编译后,需要加载到内存,如何加载呢,就是双亲委派机制来进行加载。
a、双亲委派机制
结合上图,如果有个类,要加载到内存中,先从最下边的左边
- 的CustomClassLoader 去加载,CustomClassLoader 看缓存里是否加载了,如果没有;
- 问父加载器 ApplicationClassLoader 有没有加载进来,ApplicationClassLoader 看缓存里是否加载了,如果没有;
- 问父加载器 ExtensionClassLoader 有没有加载进来,ExtensionClassLoader 看缓存里是否加载了,如果没有;
- 问父加载器 BootstrapClassLoader 有没有加载进来,BootstrapClassLoader 看缓存里是否加载了,如果没有。
- 则从右边回过头来往下走看是否是 BootstrapClassLoader 应该加载的类,如果不是;
- 则往下来看是否是ExtensionClassLoader 应该加载的类,如果不是;
- 则往下来看是否是ApplicationClassLoader 应该加载的类,如果不是;
- 则往下来看是否是 CustomCLassLoader 应该加载的类,如果是则加载进来
- 如果还不是,则报错:
ClassNotFoundException...
- 这就是双亲委派进制
- 对应的代码是
ClassLoader.loadClass()
,通过递归来实现的。
b、类加载过程
也就是双亲委派机制,如下图,更加清晰明了。
这里的缓存是:自己的类加载器所维护的容器,一个数组。将加载的类文件都放里面了。
双亲:是指从
子加载器
到父加载器
,再从父加载器
到子加载器
。不要再上面加类,这里和类是没有关系的。(这里的翻译是比较怪的)面试题必问之一:为什么要搞双亲委派机制,这么麻烦,直接放到容器不行吗?
答:因为安全,假如用反证法,假如给你任何一个class,你
自定义的class
,都可以自由的把它 load 到内存,那么给这么一个类:java.lang.String
,自定义的类,直接将oracle内部写的类覆盖掉。按说不应该,需要看其他ClassLoader 是否加载了这个类,如果加载了就不用下载了。
2、父加载器
父加载器
- 父加载器不是“类加载器的加载器”!!!!!也不是“类加载器的父类加载器”
双亲委派是一个
孩子向父亲方向,然后父亲向孩子方向的双亲委 派过程
思考:为什么要搞双亲委派
- – java.lang.String类由自定义类加载器加载行不行?(
2.2中给出了问答
)
- – java.lang.String类由自定义类加载器加载行不行?(
- 后来 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版本
8版本如下图所示ExtensionClassLoader
和 PlatformCLassLoader
是等价的,8版本之后
Launcher
类所在的包是sun.misc
。
3、类加载器范围
a、三个类加载器:
1. Bootstrap
2. ExtensionClassLoader
3. AppClassLoader
b、 sun.misc.Launcher$ExtClassLoader@1b6d3586
解析如下
三个类加载器都在 Launcher
类中,为其内部类
,$ 符号
后面的是加载器名称,也是内部类类名,@符号
后面的该类具体的 哈希code码
。
c、 Launcher类源码如下
(来自Launcher源码)
sun.boot.class.path
- Bootstrap ClassLoader加载器的 加载路径
- 截图如下:
java.ext.dirs
- ExtensionClassLoader加载器的 加载路径
- 截图如下:
java.class.path
- AppClassLoader加载器的 加载路径
- 截图如下:
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的路径啦。
4、自定义类加载器
a、三步
- 继承
ClassLoader
类 - 重写模板方法
findClass
- 调用
defineClass
- 调用
- 自定义类加载器加载自加密的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("");
}
}
T005_LoadClassByHand.class.getClassLoader()
获取的是APPClassLoader 类加载器,- 通过
loadClass
方法进行加载指定的T002_ClassLoaderLevel
类。然后将此类加载进来。 - 将此类加载 到内存,会返回class 对象:
com.mashibing.jvm.c2_classloader.T002_ClassLoaderLevel
- 题外话:loadClass() 是反射的基石。
- 什么时候去加载一个类呢?
spring 有一个动态代理,会自动加载。
c、读loadClass源码
- 先加锁
synchronized
,以防加载过程中,其他类在加载 - 首先
findLoadedClass
,先查看是否加载过了,如果没有加载过,在进行加载。底边源码直接到native了 - 父加载器再进行查找,看是否加载了,进入
递归模式委派机制
, - 如果没有找到在回来进行 加载类
findClass
。 - 所以,如果自定义类加载器,则重写
findClass
方法即可。(在上图中的最下面) - 这里是自定义
ClassLoader
,但还是在上面的双亲委派机制下的自定义类加载器。(下面会 有一个打破双亲委派机制的案例和应用)
d、自定义类加载器
d盘下的Hello.class
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());
}
}
e、加载流程
从底层加载器到顶级加载器 检查 对应缓存看是否加载过
。先进入到自定义类加载器看是否有没有被加载,若没有,则到父加载器APPClassLoader ,若APPClassLoader也没有加载过,则到其父加载器ExtCLassLoader,若ExtClassLoader也没有加载过,则到顶级父类加载器 BootstrapClassLoader ,若也没有加载。则进行下面的加载逻辑再从顶级加载器到底层加载器开始 加载 该类
。该类BootstrapClassLoader 加载器 看到该类(D:\test\com\mashibing\jvm\Hello.class
)不属于自己的加载路径,则交给ExtClassLoader加载器,ExtClassLoader 发现也不属于自己的加载路径,则交给APPClassLoader加载器,但APPClassLoader 加载器发现也不属于自己的加载路径,则交给自定义加载路径。然后加载。- 然后从
自定义的 findClass()
去定义class并进行返回。 - 因为还是双亲委派机制下的自定义类加载器,所以 clazz 加载后,在加载 clazz1 ,后者就是走的前者加载后的缓存机制,也就是双亲委派机制在起作用。所以会输出 TRUE。
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
:即时编译器。
- 混合模式
- 混合使用
解释器 + 热点代码编译
(代码先解释执行,发现有一段代码执行了上万次,则编译成本地代码执行) - 起始阶段采用解释执行
- 热点代码检测 (如果确定热点代码呢?)
- 多次被调用的方法(
方法计数器:监测方法执行频率
) - 多次被调用的循环(
循环计数器:检测循环执行频率
) - 进行编译
- 多次被调用的方法(
- 混合使用
-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秒左右。 - 然后修改配置为解释编译如下,
-Xint
是纯解释型。没有内部编译的过程。大约4.5左右。 - 改成 编译模式,
-Xcomp
大约4秒,显示区别不大,但是类特别多的情况下,区别还是很大的。
7、JVM的懒加载模式
a、懒加载
严格讲应该叫
lazyInitializing
。JVM 规范并没有规定何时加载。
JVM 严格规定了什么时候必须初始化。(五种情况进行懒加载)
new getstatic putstatic invokestatic
指令,访问final变量除外java.lang.reflect
对类进行反射调用时- 初始化子类的时候,父类首先初始化
- 虚拟机启动时,被执行的主类必须初始化
- 动态语言支持
java.lang.invoke.MethodHandle
解析的结果为- REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初 始化
- 按需加载
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");
}
}
}
三、本章总结
1、加载过程
Loading
- 双亲委派,主要出于安全来考虑
- LazyLoading 五种情况
- –new getstatic putstatic invokestatic指令,访问final变量除外
- –java.lang.reflect对类进行反射调用时
- –初始化子类的时候,父类首先初始化
- –虚拟机启动时,被执行的主类必须初始化
- –动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
- ClassLoader的源码
- findInCache -> parent.loadClass -> findClass()
- 自定义类加载器
- extends ClassLoader
- overwrite findClass() -> defineClass(byte[] -> Class clazz)
- 加密
- 第一节课遗留问题:parent是如何指定的,打破双亲委派,学生问题桌面图片
- 用super(parent)指定
- 双亲委派的打破
- 如何打破:重写loadClass()
- 何时打破过?
- JDK1.2之前,自定义ClassLoader都必须重写loadClass()
- ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
- 热启动,热部署
- osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
5. 混合执行 编译执行 解释执行 1. 检测热点代码:-XX:CompileThreshold = 10000
Linking
- Verification
- 验证文件是否符合JVM规定
- Preparation
- 静态成员变量赋默认值
- Resolution
- 将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
- 将类、方法、属性等符号引用解析为直接引用
- Verification
Initializing
- 调用类初始化代码 ,给静态成员变量赋初始值
2、小总结:
- load - 默认值 - 初始值
- 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);
}
}
解析:
因为自定义类加载器时,重写的是 findClass()
方法,所以执行的还是双亲委派机制。所以第二次 msbClassLoader = new T006_MSBClassLoader();
的时候,先进行查找是否加载过,加载过的话就找到直接返回。
因为第一次new加载过了,所以第二次new就不用加载了,找到直接返回即可。
所以打印的两个hashcode值是一样的,而且返回TRUE。
2、打破双亲委派机制
这里重写的是 loadClass()
方法,所以就双亲委派机制就被我们给替代了(因为源代码中,双亲委派机制是在 loadClass()
方法中的,这里重新了,需要我们自定义加载机制了,所以这里就可以打破双亲委派机制啦 )。
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);
}
}
解析:
首先自定义类加载器时,这里重写的是 loadClass
,所以就不会走源代码写死的双亲委派机制。
所以这里加载的逻辑是:如果类文件不存在则交给父类加载器去加载,但是一旦类文件存在,则直接加载到内存。所以就会出现,只要是自己的类文件则一定会加载。
所以这里返回的FALSE,因为 两次new 都是重新加载到内存的
所以热部署的应用也是依靠这个打破双亲委派机制而进行改造的。