JVM学习笔记2-主动使用及代码示例

主动使用的七种情形

  1. 创建类的实例
  2. 访问某个类或接口的静态变量或对该静态变量赋值
  3. 调用类的静态方法
  4. 通过Class.forName进行反射
  5. 初始化一个类的子类(也表示对父类的直接使用)
  6. JVM启动时被标记为启动类的类main方法
  7. JDK 1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的雷友初始化,则初始化

主动使用的特点

  1. 对静态字段来说,只有直接定义了该字段的类才会被初始化
  2. 当一个类在初始化时,要求其父类全部都已经初始化完毕
public class MyTest1 {
    public static void main(String[] args) {

        System.out.println(Child1.str1);
        System.out.println("+++");
        System.out.println(Child1.str2);
    }
}

class Parent1{
    public static String str1 = "hello world";
    static {
        System.out.println("parent static block");
    }
}

class Child1 extends Parent1{
    public static String str2 = "welcome";
    static {
        System.out.println("child static block");
    }
}

执行结果为:

parent static block
hello world
+++
child static block
welcome

特例

public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(Parent2.str);
    }
}

class Parent2{
    //final修饰的静态常量在编译阶段就会被存入的调用常量方法所在类的常量池中
    //本质上调用类并没有直接引用定义常量的类,因此并不会触发定义常量类的初始化
    //注意 这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与Parent2就没有任何关系了
    //甚至在编译完成后,我们可以直接将Parent2对应的class文件删掉

    public final static String str = "hello world";
    static {
        System.out.println("parent static block");
    }
}

执行结果为

hello world

public class MyTest11 {
    public static void main(String[] args) {
        System.out.println(Child11.a);
        System.out.println("++++");
        Child11.doSomething();
    }
}

class Parent11{
    static int a = 3;
    static {
        System.out.println("Parent11 static block");
    }
    static void doSomething(){
        System.out.println("do something");
    }
}
class Child11 extends Parent11{

    static {
        System.out.println("Child11 static block");
    }
}

输出结果为:

Parent11 static block
3
++++
do something

Child11静态代码块未执行的原因是:

  • a定义在Parent11
  • Child11.a是对Parent11的主动使用,而不是对Child11的主动使用

结论:

  • 通过子类访问父类的静态变量或静态方法,是对父类的主动使用,而不是对子类的主动使用

Parent2对应的静态代码块未执行的原因是

  • final修饰的静态常量在编译阶段就会被存入的调用常量方法所在类MyTest2的常量池中
  • 本质上调用类MyTest2并没有直接引用定义常量的类Parent2,因此并不会触发定义常量类Parent2的初始化

甚至在编译完成后,我们可以直接将Parent2对应的class文件删掉也不会影响Mytest2的正常执行

public class MyTest3 {
    public static void main(String[] args) {
        System.out.println(Parent3.str);
    }
}

class Parent3{
    public static final String str = UUID.randomUUID().toString();

    static {
        System.out.println("Parent static block");
    }
}

执行结果为

Parent static block
f7e9d6fd-fcf6-40a4-9b90-009d2ac12bf1

其原因是:
当一个变量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中
那么在程序运行时,也会导致主动使用的这个常量所在的类被初始化

public class MyTest4 {
    public static void main(String[] args) {
        Parent4 parent4 = new Parent4();
        System.out.println("====");
        Parent4[] parent4s = new Parent4[1];
    }
}

class Parent4{
    static {
        System.out.println("parent static block");
    }
}

执行结果为

parent static block
====

其原因是
创建类的实例也是对类的主动使用的情形之一

  • 对于数组实例来说,其类是由JVM在运行期间动态生成的,标识为class [Lmain.jvm.classloader.Parent4这种形式,动态生成的类型,其父类就是Object
  • 对于数组来说,JavaDoc经常讲构成数组的元素为Component,实际上就是将数组降低一个维度后的类型
//验证方式,编译后的依次删除Parent5 Child5的class文件
public class MyTest5 {
    public static void main(String[] args) {
        System.out.println(Parent5_1.thread);
    }
}
interface Grandpa5{
    public static int a = new Random().nextInt(5);
    public static Thread thread1 = new Thread(){
        {
            System.out.println("Grandpa invoked");
        }
    };
}
interface Parent5{
    public static int b = new Random().nextInt(5);

    public static Thread thread2 = new Thread(){
        {
            System.out.println("parent5 invoked");
        }
    };
}
interface Grandpa5_1{
    public static int a = new Random().nextInt(5);

    public static Thread thread = new Thread(){
        {
            System.out.println("Grandpa5_1 invoked");
        }
    };
}
interface Parent5_1{
    public static int b = new Random().nextInt(5);

    public static Thread thread = new Thread(){
        {
            System.out.println("Parent5_1 invoked");
        }
    };
}
interface Child5 extends Parent5{
    public static int c = 6;
}

输出结果

Parent5_1 invoked
Thread[Thread-0,5,main]

说明:

  • 当一个接口在初始化时,并不要其父接口都完成初始化
  • 只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会被初始化
public class MyTest6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("count1="+Singleton.count1);
        System.out.println("count2="+Singleton.count2);
    }
}

class Singleton{
    public  static int count1 = 1;
    private static Singleton singleton = new Singleton();
    private Singleton(){
        System.out.println("construct block");
        count1++;
        count2++; //准备阶段的重要意义

        System.out.println("construct count1="+Singleton.count1);
        System.out.println("construct count2="+Singleton.count2);
    }

    public static int count2 = 0;
    public static Singleton getInstance(){
        return singleton;
    }
}

输出结果:

construct block
construct count1=2
construct count2=1
count1=2
count2=0

public class MyTest12 {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> clazz = loader.loadClass("main.jvm.classloader.CL");
        System.out.println(clazz);
        System.out.println("+++");
        clazz = Class.forName("main.jvm.classloader.CL");
        System.out.println(clazz);
    }
}
class CL{
    static {
        System.out.println("CL static block");
    }
}

class main.jvm.classloader.CL
+++
CL static block
class main.jvm.classloader.CL

结论:

  • 反射会导致类的初始化,即对类进行主动使用
  • 加载类则不是对类的主动使用

JVM学习笔记3-类的加载,连接和初始化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值