学习笔记

深入理解JVM_类加载1
Publish: 2020/2/28   

类加载

基本概念

类加载器深入刨析

类的使用过程

类的加载、连接与初始化

主动使用(七种)

被动使用

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


import java.util.UUID;

/**
 * -XX:+TraceClassLoading,用于追踪类的加载信息并打印出来
 * -XX:+<option>,表示开启option选项
 * -XX:-<option>,表示关闭option选项
 * -XX:<option>=<value>,表示将option选项的值设置为value
 */
public class Main {

    public static void main(String[] args) {
        //Example 1
        System.out.println(Child.stringParent);
        /*
        输出结果
        Parent static block
        hello world
        对于静态字段来说,只有直接定义了该字段的类才会被初始化。
        */

        //Example 2
        System.out.println(Child.stringChild);
        /*
        输出结果
        Parent static block
        Child static block
        welcome
        初始化一个类的子类时,要求它的父类都已经被初始化完毕。
        */

        //Example 3
        System.out.println(Parent1.stringParent1);
        /*
        输出结果
        hello parent1
        在编译阶段,常量就会被存入到调用这个常量的方法所在的类的常量池当中,
        本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量类
        的初始化。

        注意:这里指的是将常量存放到了Main的常量池中,
        之后Parent1与Main就没有任何关系了,
        甚至可以将Parent1的class文件删除。
        */

        //Example 4
        System.out.println(Parent2.stringParent2);
        /*
        输出结果
        Parent2 static block
        71bd2365-e20c-4d3e-b071-2b56287afadf
        当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中,
        这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化。
        */

        //Example 5
        Parent3[] parent3s=new Parent3[1];
        System.out.println(parent3s.getClass());
        System.out.println(parent3s.getClass().getSuperclass());
         /*
        输出结果
        class [LParent3;
        class java.lang.Object
        对于数组实例来说,其类型时有JVM在运行期动态生成的,表示为[LParents3这种形式。
        动态生成的类型,其父类型就是Object
        对于数组来说,JavaDoc经常将构成数组的元素称为Component,实际上就是将数组降低一个维度后的类型。

        ints.getClass()       Class [i
        chars.getClass()      Class [C
        booleans.getClass()   Class [Z
        shorts.getClass()     Class [S
        bytes.getClass()      Class [B
        */

        //Example 6
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1: " + Singleton.counter1);
        System.out.println("counter2: " + Singleton.counter2);
        System.out.println("counter3: " + Singleton.counter3);
        /*
        输出结果
        Singleon() counter1: 1
        Singleon() counter2: 2
        Singleon() counter3: 1
        counter1: 1
        counter2: 2
        counter3: 0

        准备阶段赋默认值,初始化是从上到下顺序执行。
        */
    }
}

//Example 1
class Parent {
    public static String stringParent = "hello world";

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

//Example 2
class Child extends Parent {
    public static String stringChild = "welcome";

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

//Example 3
class Parent1 {
    public static final String stringParent1 = "hello parent1";

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

//Example 4
class Parent2 {
    public static final String stringParent2 = UUID.randomUUID().toString();

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

//Example 5
class Parent3 {

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

//Example 6
class Singleton {

    public static int counter1;

    public static int counter2 = 1;

    private static Singleton singleton = new Singleton();

    private Singleton() {
        counter1++;
        counter2++;
        counter3++;

        System.out.println("Singleon() counter1: " + counter1);
        System.out.println("Singleon() counter2: " + counter2);
        System.out.println("Singleon() counter3: " + counter3);
    }

    public static int counter3 = 0;

    public static Singleton getInstance() {
        return singleton;
    }
}

助记符:

类的加载

类加载器

类加载器用来把类加载到Java虚拟机中。从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能更好的保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当Java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父类加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。

类的验证

类的准备

在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如对于以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0。

类的初始化

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。
在程序中,静态变量的初始化有两种途径:

类的初始化步骤:

类的初始化时机:

类加载和类初始化深度刨析


import java.util.UUID;

public class Main {

    public static void main(String[] args) {
        System.out.println(Child5.c);
        System.out.println(Child4.b);
        System.out.println(Parent4.a);
        /*
        输出结果
        5
        随机0 ~ 3;
        0
        classLoader.Parent4 invoked

        当一个接口在初始化时,并不要求其父接口都完成了初始化,
        只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化
        */

    }
}

import java.util.Random;

public interface Parent4 {
    public static final int a = new Random().nextInt(4);

    public static final Thread thread = new Thread() {
        {
            System.out.println("classLoader.Parent4 invoked");
        }
    };
}

interface Child4 extends Parent4 {
    public static final int b = new Random().nextInt(4);
}

class Child5 implements Child4 {
    public static int c = 5;
}

package classLoader;

public class Test {
    public static void main(String[] args) {
        System.out.println(Child6.a);
        System.out.println("-----------");
        Child6.doSomething();
        /*
        输出结果:
        Parent6 static block
        3
        -----------
        do something

        如果用子类类名访问父类的静态变量或静态方法,
        都表示对父类的主动使用,而不是对子类的主动使用。
        */
    }
}

class Parent6{
    static int a = 3;

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

    static void doSomething(){
        System.out.println("do something");
    }
}

class Child6 extends Parent6{
    static {
        System.out.println("Child6 static block");
    }
}

package classLoader;

public class Test2 {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class<?> clazz = classLoader.loadClass("classLoader.CL");
        System.out.println(clazz);
        System.out.println("---------");
        clazz = Class.forName("classLoader.CL");
        System.out.println(clazz);
        /*
        class classLoader.CL
        ---------
        Class CL
        class classLoader.CL

        调用ClassLoader类的loadClass方法加载一个类,
        并不是对类的主动使用,不会导致类的初始化。
        */
    }
}

class CL{
    static {
        System.out.println("Class CL");
    }
}


← 深入理解JVM_类加载2 Android源码设计模式_8观察者模式 →

Powered by Hexo, Theme designs by @hpcslag.
Style-Framework Tocas-UI designs by @yamioldmel