1.首先了解JVM(java虚拟机)、JRE和JDK之间的关系
JDK全称是Java SE Development Kit(Java开发工具),提供了编译和运行Java程序所需的各种资源和工具,包括JRE+java开发工具。
JRE全称是Java runtime environment(java运行环境),包括虚拟机+java的核心类库。
JVM是运行java程序的核心虚拟机。
2. 类的加载过程
在java代码中,类型的加载、连接与初始化过程都在程序运行期间完成的。
提供了更大的灵活性,增加了更多的可能性。
类从被加载到虚拟机内存中开始,到卸载出内存结束,它的整个生命周期包括
加载:通俗的说,也就是将class文件放到java虚拟机中,让jvm去加载类的二进制数据
连接:连接可以分为3个步骤
验证:验证被加载的类的准确性
准备:为类的静态变量分配内存,并将其初始化为默认值。
解析:把类中的符号引用转换成直接引用。也就是说去解析这个类中所包含的其他类型的引用
初始化:为类中静态变量赋予正确的初始值。
使用:使用这个类的过程
卸载:GC的回收
3.JVM是什么时候结束生命周期的
1.执行System.exit()方法
2.程序的正常执行结束
3.程序在执行过程中遇到了异常或者错误而被终止
4.由于操作系统出现错误而导致java虚拟机进程终止
4.java程序对于类的使用方式可以分为两种
主动使用
1.创建类的实例
2.访问某个类或接口的静态变量,或者对该静态变量赋值
3.调用类的静态方法
4.反射
5.初始化一个类的字类
6.java虚拟机启动时被标明为启动类的类
7.jdk1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果
REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。
被动使用
除了上面其中情况之外,其他使用java类的方法都被看做类的被动使用,都不会当作类的初始化。
5.类的加载方式
1.从本地系统中直接加载
2.通过网络下载class文件
3.从zip,jar等归档文件中加载class文件
4.从专有数据库中提取class文件
5.将java源文件动态编译为class文件
下面开始对于类的加载过程做个demo,并查看结果
1.
package com.example.demo.test;
public class MyTest1 {
public static void main(String[] args){
System.out.println(MyChild.str);
}
}
class MyParent{
public static String str="hello world";
static {
System.out.println("this is MyParent");
}
}
class MyChild extends MyParent{
public static String str2="weclome";
static {
System.out.println("this is MyChild");
}
}
执行一下发现结果是
this is MyParent
hello world
也就是说Mychild的静态代码块没有被执行
使用JVM options去查看jvm执行顺序
执行结果如下
可以看出第一个被加载出来的是它的父类parent被加载,又由于它调用的父类的str所以打印了第二行。说明child只是被加载,而没有被初始化。
2.当我去调用str2的时候
package com.example.demo.test;
public class MyTest1 {
public static void main(String[] args){
System.out.println(MyChild.str2);
}
}
class MyParent{
public static String str="hello world";
static {
System.out.println("this is MyParent");
}
}
class MyChild extends MyParent{
public static String str2="weclome";
static {
System.out.println("this is MyChild");
}
}
发现执行结果如下
当它调用MyChild.str2的时候,它首先加载并初始化的是它的父类,所以打印了第一行,然后去加载child,调用str2实现之前实现对child的初始化,也就是打印了第二行,最后再去调用str2。
总结:对于静态字段来说,只有直接定义了该字段的类才会被初始化,当一个类在初始化时,要求其父类全部都初始化完毕。
附:-XX:+<option>,表示开启option选项
-XX:-<option>,表示关闭option选项
-XX:<option>=<value>,表示将optaion选项的值设置为value
下面开始对于类的常量加载过程做个demo,并查看结果
package com.example.demo.test;
public class MyTest1 {
public static void main(String[] args){
System.out.println(MyChild.str2);
}
}
class MyParent{
public static String str="hello world";
static {
System.out.println("this is MyParent");
}
}
class MyChild extends MyParent{
public static final String str2="weclome";
static {
System.out.println("this is MyChild");
}
}
调用str2的常量,执行结果是
也就是说没有进行加载和初始化
直接将child和parent的class文件删除,发现常量在编译阶段会存入到调用这个常量的类的常量池中
总结:调用类并没有直接引用到定义常量的类,因此并不会触发,由执行上面代码可以得出结果str2在MyTest1的编译过程中就已经放到MyTest1的类所属的常量池中,之后的调用就和parent和child类无关,甚至可以将parent和child类文件删除。