Java 类加载
类编译
.class 文件包含哪些信息 ?
类加载的过程
参考:《深入理解 Java 虚拟机》
JVM 把 Class 文件加载到内存,然后进行校验、准备、解析、初始化,最终形成可以使用的 Java 类型,这就是类加载机制。其中,校验、准备、解析这三个阶段,放在一起是链接阶段。
- 【加载】二进制字节流可以从 JAR、WAR、网络、运行时动态生成(动态代理)、JSP生成。
- 【验证】文件格式、元数据(信息语义)、字节码验证(类型转换是否有效、跳转指令不会跳到方法体以外的地方去)、符号引用验证。在同一个类中,如果同时出现多个名字相同且描述符也相同的方法,那么 Java 虚拟机会在此阶段报错。
- 【准备】
static final
变量赋值、各个基本数据类型的默认值 - 【解析】常量池内的符号引用(用符号描述引用目标)替换为直接引用(直接指向目标的指针)
- 【初始化】编译器收集 static 块、类变量的赋值放到 () 方法中
符号引用存储在哪里
在编译过程中,我们并不知道目标方法的具体内存地址。因此,Java 编译器会暂时用符号引用来表示该目标方法。符号引用存储在 class 文件的常量池之中。根据目标方法是否为接口方法,这些引用可分为接口符号引用和非接口符号引用。利用“javap -v”打印某个类的常量池。
加载器类型
Bootstrap ClassLoader
:加载JAVA_HOME/lib
、或者-Xbootclasspath
,并且是按照名字识别的,如 rt.jarExtension ClassLoader
:JAVA_HOME/lib/ext
、或者java.ext.dirs
系统变量Application ClassLoader
:加载用户ClassPath
上的类库
类加载器之间的层次关系,称之为双亲委派模型。如果一个类收到类加载请求,那么会首先委派给父类,父类反馈无法完成,自己才进行加载。
何时初始化
- 遇到
new
、getstatic
、putstatic
、invokestatic
这 4 条字节码指令。对应的是 new 一个对象、读取静态字段、设置静态字段(字段没有被final
修饰,否则在编译器就已经将结果放在常量池了)、调用一个类的静态方法。 java.lang.reflect
进行反射。- 初始化 A 类,其父类没有被初始化。
- 虚拟机启动时,包含
main()
方法的这个类
// 不会导致子类初始化
println(SubClass.parentStaticValue);
// 不会初始化 SuperClass
SuperClass[] sca = new SuperClass[10];
// 不会导致 ConstClass 初始化
println(ConstClass.staticFinalValue);
静态变量何时加载
static final
: 编译器优化这种常量,然后将这个值直接内嵌到 bytecode 里面static
: 随着这个类的加载而加载
Tomcat 类加载机制
Tomcat 的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object
,String
等),各个 web 应用自己的类加载器(WebAppClassLoader
)会优先加载,加载不到时再交给 CommonClassLoader
走双亲委托。
如果我自己定义一个恶意的
HashMap
,会不会有风险呢?
Tomcat 不遵循双亲委派机制,只是自定义的 ClassLoader 顺序不同,但顶层还是相同的,还是要去顶层请求 ClassLoader
。
Tomcat 如果使用默认的类加载机制行不行?
如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
Tomcat 热修改
Jsp 文件其实也就是 class
文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的 Jsp 是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这 Jsp 文件的类加载器,所以你应该想到了,每个 Jsp 文件对应一个唯一的类加载器,当一个 Jsp 文件修改了,就直接卸载这个 Jsp 类加载器。重新创建类加载器,重新加载 Jsp 文件。