jvm——类加载器

类的生命周期

加载

目的

将.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
29
public 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
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
public class MyTest1 {

public static void main(String[] args) {

System.out.println(MyChild1.STR);
}
}

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
hello world

结论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
30
public 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
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
30
31
public class MyInterfaceInitTest {

public static void main(String[] args) {
System.out.println(MyInterface.thread);
}
}

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;
}
执行结果:
MyInterface block
Thread[Thread-0,5,main]

证明4

final修饰的常量如果是确定的值,在编译期就已经初始化并分配在了常量池,如果编译期无法确定则需要运行期初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public 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
28
public 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

显示 Gitment 评论