最近阅读《深入理解JVM》,整理出以下资料,与大家分享。由于写作水平和阅历有限,本中存在不妥、不全之处,还请大家多多留言。
目录
类加载机制:
JVM将Class文件加载到内存,并对其进行校验、转换解析和初始化,最终形成被JVM直接使用的Java类型。
类的生命周期
类的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、和卸载(Unloading)7阶段。从加载至JVM内存开始,到卸载出内存。
类的加载时机
-
初始化时机
-
类主动引用
-
遇到new、getstatic、putstatic、或invokestatic这4条字节码指令时。
-
遇到java.lang.reflect包的方法对类进行反射调。
-
初始化子类时,则先初始化父类。
-
虚拟机(JVM)启动时,用户指定程序的主类。
-
使用JDK1.7时,如果一个java.lang.invoke.MethodHandler实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且句柄对应的类没有进行初始化,则先触发其初始化。
-
被动引用
1、通过子类引用父类的静态字段,不会导致子类初始化
代码
/**
* 被动使用字段演示1
* 通过子类引用父类的静态字段,不会导致子类初始化
* @author Bad Written
*
*/
public class SuperClass {
static{
System.out.println("SuperClass init!");
}
public static int value=888;
}
public class SubClass extends SuperClass {
static{
System.out.println("SubClass init!");
}
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(SuperClass.value);
}
}
运行结果:
SuperClass init!
888
2、通过数组定义来引用类,不会触发此类初始化。创建动作有字节码指令newarray触发。
代码
/**
* 被动使用字段演示2
* 通过数组定义来引用类,不会触发此类初始化
* @author Bad Written
*
*/
public class SuperClass {
static{
System.out.println("SuperClass init!");
}
public static int value=888;
}
public class SubClass extends SuperClass {
static{
System.out.println("SubClass init!");
}
}
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] sup=new SuperClass[10];
}
}
运行结果:
3、常量在编译阶段会存入常量池中,并没有直接引用到定义常量的类,因此不会触发常量的类初始化。
代码:
/**
* 被动使用类字段演示3
* 常量在编译阶段会存入常量池中,并没有直接引用到定义常量的类,因此不会触发常量的类初始化。
* @author Bad Written
*
*/
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLO="hello word";
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO);
}
}
运行结果:
hello word
-
接口初始化
在初始化时,区别在于类主动引用第3种:在子接口初始化时,只有在用到父接口,才会初始化。
类加载过程
-
加载
- 通过某个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流的静态存储结构转换为方法区运行时数据结构。
- 在内存中生成此类的java.lang.Class对象,作为方法区此类的访问入口。
-
验证
目的:确保Class文件不危害JVM自身的安全。
验证阶段非常重要、但不是一定必要的阶段,可以对反复使用和验证过正确的,可考虑使用 -Xverify:none参数来关闭大部分类验证,以缩短虚拟机类加载时间。
大致分为4个阶段:
-
文件格式验证:验证字节流是否符合Class文件格式规范。(此阶段基于二进制字节流进行,后面3个阶段基于方法区的存储结构)
-
元数据验证:字节码描述信息进行验证。
-
字节码验证:验证程序语义是否合法、符合逻辑。
-
符号引用验证:对类自身以外的信息进行匹配校验。
-
准备
为类变量分配内存并设置类变量初始值。
-
解析
将常量池内的符号引用替换为直接引用的过程。
- 符号引用(Symbolic References):用一组符号来描述所引用的目标,符号可以是任何形式的字面量。
- 直接引用(Direct References):可以是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
-
初始化
主观计划去初始化类变量和其他资源。
类加载器
把类加载阶段中的“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到Java JVM外部实现。实现这个动作模块称为“类加载器”。
-
类与类加载器
比较2个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则同一个Class文件,类加载器不同,则这两个类必定不相等。
package org.student.badwritten.load;
import java.io.IOException;
import java.io.InputStream;
/**
* 类加载器演示
* @author Bad Written
*
*/
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//简单的类加载器
ClassLoader myload=new ClassLoader() {
public Class<?> loadClass(String name) throws ClassNotFoundException{
try {
//获取文件的全称
String filename=name.substring(name.lastIndexOf(".")+1)+".class";
//从当前类所在的包下查找该资源
InputStream input=getClass().getResourceAsStream(filename);
if(input==null){
//加载 类名.class 字节码文件的工具
return super.loadClass(name);
}
byte[] b=new byte[input.available()];
input.read(b);
//将定义的字节码文件经过字节数组流解密之后,将该字节流数组生成字节码文件
return defineClass(name, b, 0, b.length);
}catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj=myload.loadClass("org.student.badwritten.load.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof org.student.badwritten.load.ClassLoaderTest);
}
}
-
双亲委派模型
类加载器的双亲委派模型(Parents Delegation Model):除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间父子关系一般使用组合(Composition)关系来复用父加载器代码。
3个类加载器用途:
-
启动类加载器(Bootstrap ClassLoader):将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机(JVM)中。
-
扩展类加载器(Extension ClassLoader):加载<JAVA_HOME>\lib\ext目录中所有类库。
-
应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库。
过程:
-
类加载器收到一个加载请求。
-
委派给父类加载器完成,每层都如此。
-
最终将加载请求传至顶层的启动类加载器中,判断是否能完成加载请求,完成便加载,完成不了,子加载器尝试加载。
特点:java类具备优先级层次关系。
-
启动类加载器(Bootstrap ClassLoader):将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机(JVM)中。
-
扩展类加载器(Extension ClassLoader):加载<JAVA_HOME>\lib\ext目录中所有类库。
-
应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库。
-
破坏双亲委派模型
在OSGi环境下,类加载器不再是双亲委派模型中树状结构,而是复杂的网状结构。
当收到类加载请求时,OSGi按照下面顺序进行类搜索:
- 将以java.*开头的类为派给福类加载器加载。
- 否则,将委派列表单内的类委派给父类加载器加载。
- 否则,将import列表中的类为派给Export这个类的Bundle的类加载器加载。
- 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
- 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
- 否则,查找Dynamic Import列表的Bundle,委派给对应的类加载器加载。
- 否则,类查找失败。