类加载机制(二):类的初始化


title: 类加载机制(二):类的初始化
date: 2019-03-13 18:49:55
categories:

  • Java虚拟机
    tags:
  • 类加载机制
  • 类的初始化

类的初始化

引言

一般Java程序的class文件经过加载、连接后,就进入初始化阶段,顺序执行static语句,为静态变量赋予正确的值,执行static代码块,初始化类。

类的使用方式

Java程序对类的使用分为两种:

----主动使用

----被动使用

所有的Java虚拟机实现必须在每个类或接口被Java程序首次主动使用时才会初始化它们。

主动使用方式

主动使用分为七种:

----创建类的实例

----访问某个类或接口的静态变量,或者对该静态变量赋值

----调用类的静态方法

----反射(如Class.forName(com.test.Test)

----初始化一个子类

----Java虚拟机启动时被标明为启动类的类

----JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。

除了以上七种情况,其他使用Java类的方法都被看作是对类的被动使用,都不会导致类的初始化。

类的初始化步骤

对于类来说:

假如这个类还没有被加载和连接,那就先进行加载和连接

假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化父类

假如类中存在初始化语句,那就一次执行这些初始化语句

对于接口来说:

Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

在初始化一个类时,并不会先初始化它所实现的接口

在初始化一个接口时,并不会先初始化它的父接口

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

示例
NO.1
public class MyTest1 {
   
    public static void main(String[] args) {
   
        System.out.println(Son.str);
    }

}
class Parent{
   
    public static String str = "parent str";
    static {
   
        System.out.println("parent static启动");
    }
}
class Son extends Parent{
   
    static {
   
        System.out.println("son static启动");
    }
}

输出结果

输出结果显示只有Parent类被加载了。对于静态字段来说,只有直接定义了该字段的类才会被初始化。虽然Son没有被主动使用,但它已经被加载了。类加载器并不需要等到某个类被首次主动使用时再加载它。

Parent类中的str变量注释掉,添加到Son类中

输出结果:

输出结果显示Parent类与Son类都被初始化了。通过使用Son的静态变量,导致Son的初始化,而当一个类在初始化时,首先要求其父类全部都已经初始化,即导致Parent初始化。

我们还可以从第一段打印类加载信息(通过添加虚拟机参数-XX:+TraceClassLoading)看出,虽然Son没有被主动使用,但它已经被加载了。类加载器并不需要等到某个类被“首次主动使用”时再加载它。

NO.2
public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(Parent2.str);
        System.out.println(Parent2.bi);
        System.out.println(Parent2.si);
        System.out.println(Parent2.icons_1);
        System.out.println(Parent2.iconst_2);
    }
}
class Parent2{
    public static final String str = "Hello Jvm";
    public static final int bi = 127;
    public static final int si = 32767;
    public static final int icons_1 = 1;
    public static final int iconst_2 = 2;
    static {
        System.out.println("Parent2 init");
    }
}

输出结果

Hello Jvm
127
32767
1
2

Parentclass文件从classPath中删除掉,再运行程序,程序没报错,输出结果一样。

常量的本质含义:常量在编译阶段会存入调用这个常量的常量池中。本质上,调用这个常量并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化。如:Paren2中定义的常量被存入到了MyTest2中,之后两个类就没有任何关系了。甚至将Paren2.class文件删除也没关系。

public class MyTest3 {
    public static void main(String[] args) {
        System.out.println(Parent3.str);
    }
}
class Parent3{
    public static final String str = UUID.randomUUID().toString().replace("-","");
    static {
        System.out.println("Paren3 init");
    }
}

输出结果:

Paren3 init
2b00eb3dbd934bf7ab610407058d276f

输出结果显示Parent3被成功初始化了。而且,删除掉Parent3class文件,也会报java.lang.NoClassDefFoundError的错误。

在编译期间,对于并不能确定的常量来说,不会被存入到调用类的常量池中。而是在运行期间,主动使用常量的所属类,完成所属类的初始化。

NO.3
public class MyTest4 {
   
    public static void main(String[] args) {
   
//        Parent4 pare
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值