类加载器深入解析
听课笔记
首先推荐两个JDK自带的监控JVM的工具jConsole、jvisualvm和JDK自带的监控命令jmap。
1.在Java代码中,类型(是指对象的类型不是指某个对象,因为这个时候class文件还没有在内存中加载完成,还不能使用它创建对象)的加载,连接与初始化过程都是在程序运行期间完成的
2.提供了更大的灵活性,增加了更多的可能性。
在如下几种情况下,java虚拟机将结束生命周期(java虚拟机本身就是一个进程):
1.执行了System.exit()方法
2.程序正常执行结束
3.程序程序在执行过程中遇到了异常或错误而异常终止(当错误或异常没被处理一直抛出到main(线程)方法时JVM进程就会终止,在spring框架的web项目中每次请求JVM都会重新产生一个新的线程,报错只是导致当前线程终止,错误不会抛出到main(进程)方法所以spring的web项目中JVM进程不会因为某个接口的报错而终止宕机。只有异常出现在main(进程)方法中时才会导致JVM终止。)
4.由于操作系统出现错误而导致Java虚拟机进程终止
一、类在JVM中的生命周期
- 加载
类的加载指的是将类的class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。
加载class文件的方式
1.从本地系统中直接加载
2.通过网络下载class文件
3.从zip,jar等归档文件中加载class文件
4.从专有数据库中提取class文件
5.将Java源文件动态编译为class文件(如:jsp在运行时被编译成servlet Java类对应的class文件从而被JVM加载) - 连接
1.验证:确保被加载的类的正确性
2.准备:为类的静态变量分配内存,并将其初始化为默认值(如int类型默认值为0)
3.解析:把类中的符号引用转换为直接引用 - 使用
主动使用(具体怎么算主动使用应该在字节码层面上去分析,转化在代码层次上可分为大概六种场景)
1.创建类的实例
2.访问某个类或接口的静态变量(包括运行期静态常量,但是编译期静态常量除外),或者对该静态变量赋值,调用类的静态方法(这里的类指定义了该静态变量或静态方法的类如下图main方法中执行run方法,由图2可知run方法是由myParent类定义,故这里算是对myParent类的主动使用,不算对myChild类的主动使用,不会初始化myChild类)
3.反射(如Class.forName(“yang.jvm.diyijiang.Demo”))
4.初始化一个类的子类(初始化子类的时候也相当于对父类的主动使用)
注: 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。
在初始话一个类时,并不会先初始化它所实现的接口
在初始化一个接口时,并不会先初始化它的父接口
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化,只有当程序首次主动使用这个父接口的静态变量时,才会导致该接口的初始化。
5.Java虚拟机启动时被表明为启动类的类(包涵main方法的类,如:执行命令Java Demo)
6.JDK1.7开始提供的动态语言支持: Java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。
被动使用:除了上述六种情况,其他使用Java类的方式都被看作是对类的被动使用
-
初始化:为类的静态变量赋予正确的初始化值(由连接-准备时赋予的默认值改为程序员设置的值),执行类中的静态代码块。
1.所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们即某个类或者接口只有在满足主动使用介绍的六种场景的时候才会被JVM初始化。
2.初始化的类必然进行了加载与连接,不初始化的类有可能进行了加载和连接也有可能没进行加载和连接。
3.每个类或接口只被JVM初始化一次,第一次使用后的使用都不会再出发初始化,而是直接取来用。
4.当一个类在初始化时,要求其父类全部都已经初始化完毕了。 -
卸载:将class字节码文件在JVM里销毁,销毁后的类型就不能使用了。
二、代码演示主动使用和被动使用并解释
示例1:
public class Demo {
public static void main(String[] args) {
System.out.println(myChild.str2);
}
}
class myParent{
public static String str1="myParent";
static{
System.out.println("myParent static block");
}
}
class myChild extends myParent{
public static String str2="myChild";
static{
System.out.println("myChild static block");
}
}
运行程序打印结果:
myParent static block
myChild static block
myChild
解释:
首先main中myChild.str2对于myChild 类来说满足主动使用的第二种场景,故myChild类被初始化;myChild类被初始化对于myParent类来说满足主动使用的第四中场景,故myParent被初始化。由于当一个类在初始化时,要求其父类全部都已经初始化完毕了,所以会先初始化myParent类,所以打印出如上结果。
示例2:
public class Demo {
public static void main(String[] args) {
System.out.println(myChild.str1);
}
}
class myParent{
public static String str1="myParent";
static{
System.out.println("myParent static block");
}
}
class myChild extends myParent{
public static String str2="myChild";
static{
System.out.println("myChild static block");
}
}
运行程序打印结果:
myParent static block
myParent
解释:
相比于示例1这里myChild 没有被初始化,解释见上文六种主动使用场景的第二条,静态变量和上面的静态run方法一样。