类的生命周期
加载
目的
将.class文件导入到jvm中
流程
- jvm读取.class文件的二进制文件
- 将class的文件描述存放到方法区中
- 同时生成一个唯一对应的java.lang.Class对象描述数据结构(jvm规范并没有明确规定该对象必须放在何处,主流的hotspot虚拟机是生成在堆上的,方法区存放的只是元数据描述)
加载方式
- 从本地的classpath路径下加载
- 从网络上下载.class文件加载
- 从.zip或.jar等压缩包中加载
- 动态加载.class文件(动态代理)
- 从特定的数据库加载.class文件,没有找到好的例子说明
最终产品
位于堆中的class对象,该对象封装了类在方法区的数据结构,并且为程序员提供了访问方法区数据的接口,是一切反射的入口
连接
验证
- 类文件的结构检查
- 语义上的检查
- 字节码检查
- 二进制兼容性检查
准备
为类的静态变量分配内存,并初始化为默认的值(这里不是代码中赋予的值,比如int默认是0,boolean默认是false,引用类型默认是null)
补充说明:jdk1.7及之前,静态变量分配内存在方法区,但是1.8之后为了配合永久代的移除,将静态变量分配在了Class对象中,即堆内存中
解析
将代码中的符号连接转换成直接连接(地址)
初始化
目的
为类的静态变量赋予正确的初始值,即程序员代码中设置的值
概念
jvm规范规定,所有java虚拟机必须在每个类或接口被java程序首次主动使用时初始化
类的使用
- 主动使用
实例化一个类,即new关键字
调用一个类或接口的静态字段
调用一个类的静态方法
使用反射
实例化一个子类,主动使用要求子类初始化时要求父类必须已初始化
一个类是一个启动类,即含有main方法
jdk1.7+中支持的动态语言的调用 - 被动使用
除以上7种以外的其他方式都属于被动使用
结论
- 对于静态变量的调用,只有直接定义了该静态变量的类才会被初始化
- 当jvm初始化一个类的时候,要求其父类已经初始化完毕,但这并不适用于接口
- 一个接口不要求其子接口或实现类在初始化时将其初始化
- final修饰的常量如果是确定的值,在编译期就已经初始化并分配在了常量池,如果编译期无法确定则需要运行期初始化
- 假如一个类存在静态变量,jvm按照文件中定义的顺序将其初始化
- 以上证明放在文末
使用
比如new一个对象
卸载
暂时缺省
类加载器类型
根加载器
Bootstrap,属于jvm自带的加载器,无父类加载器;负责加载jvm的核心类库,如java.lang.*,Object类就是由根加载器加载进来的;实现依赖底层的操作系统,属于jvm实现的一部分,并不继承ClassLoader
扩展加载器
Extension,父加载器是Bootstrap;加载java.ext.dirs指定目录下的类库,或者从jdk的安装目录jre/lib/ext下加载类库;ClassLoader的子类
系统/应用加载器
System,父加载器是Extension;从classpath路径下加载类;是所有自定义加载器的默认父加载器
自定义加载器
自定义加载器一定是属于ClassLoader的子类,其父类加载器默认是System
扩展知识
类加载
类加载器并不需要等某个类首次主动使用时再加载它。jvm规范允许类加载器在预料某个类将要被使用的时候就预先加载它,如果在预先加载的时候遇到错误(class文件丢失),类加载器必须在程序首次主动使用的时候报错,如果这个类一直没有被使用,那么不会报错
vm option
jvm参数形式只有3种:
- -XX: +option 开启option选项
- -XX: -option 关闭option选项
- -XX: option=value 设置option选项的值为value
比如:设置-XX:+TraceClassLoading可以打印jvm加载类的情况
助记符
.class文件字节码中java虚拟机指令,通过javap反编译即可看到
- ldc,表示将int,float,String类型的常量值从常量池推送至栈顶
- bipush,表示将单字节(-128~+127)的常量值推送至栈顶
- sipush,表示将一个短整型(-32768~+32767)常量值推送至栈顶
- iconst_1,表示将int类型1推送至栈顶(iconst_m1~iconst_5)jvm表示-1~5比较常用
代码证明
结论1
对于静态变量的调用,只有直接定义了该静态变量的类才会被初始化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class MyTest1 {
public static void main(String[] args) {
System.out.println(MyChild1.STR2);
}
}
class MyParent1 {
public static String STR = "hello world";
static {
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1 {
public static String STR2 = "welcome";
static {
System.out.println("MyChild1 static block");
}
}
执行结果:
MyParent1 static block
MyChild1 static block
welcome
1 | public class MyTest1 { |
结论2
当jvm初始化一个类的时候,要求其父类已经初始化完毕,但这并不适用于接口
可以参考结论1当中的代码
结论3
一个接口不要求其子接口或实现类在初始化时将其初始化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class MyInterfaceInitTest {
public static void main(String[] args) {
System.out.println(MyClass.b);
}
}
interface MyParent {
public static final Thread thread = new Thread() {
{
System.out.println("MyParent block");
}
};
}
interface MyInterface extends MyParent {
public static final Thread thread = new Thread() {
{
System.out.println("MyInterface block");
}
};
}
class MyClass implements MyInterface {
public static final int b = 5;
}
执行结果:
5
1 | public class MyInterfaceInitTest { |
证明4
final修饰的常量如果是确定的值,在编译期就已经初始化并分配在了常量池,如果编译期无法确定则需要运行期初始化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.STR);
}
}
class MyParent3 {
public static final String STR = "hello world";
static {
System.out.println("MyParent3 static block");
}
}
执行结果:
hello world
执行后,删除MyParent3.class文件,再次执行结果一样。因为在编译期的时候,STR已经分配在了MyTest3类对应的java.lang.Class对象的常量池中,之后MyParent3.class文件存不存在就没有关系了。
证明5
假如一个类存在静态变量,jvm按照文件中定义的顺序将其初始化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class MyTest6 {
public static void main(String[] args) {
System.out.println("count1:" + Singleton.count1);
System.out.println("count2:" + Singleton.count2);
}
}
class Singleton {
public static int count1;
public static Singleton singleton = new Singleton();
private Singleton() {
count1++;
count2++; // jvm准备阶段的意义,还没有初始化,即有了一个默认值
}
public static int count2 = 0;
public static Singleton getInstance() {
return singleton;
}
}
执行结果:
count1:1
count2:0