深入理解JVM-类加载及类加载器 (3)

image-20200208173021317

常量池的概念: /* 常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中,本质上,调用类并没有直接用用到定义常量的类,因此并不会触发定义常量的类的初始化。 注意:这里指的是将常量存放到了MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了。 甚至,我们可以将MyParent2的Class文件删除 */ public class MyTest2{ public static void main(String[] args){ System.out.println(MyParent2.str); } } class MyParent2{ public static final String str = "hello world"; public static final short s = 127; public static final int a = 3; public static final int m = 6; static { System.out.println("Myparent2 static block");// 这一行能输出吗?不会 } } 反编译

javap -c com.erwa.jvm.class.mytest2

反编译之后会有助记符。

助记符:

ldc表示将int、float或是String类型的常量值从常量池中推送至栈顶。

bipush表示将单字节(-128~127)的常量值推送至栈顶。

sipush表示将短整型(-32767~32768)的常量值推送至栈顶。

inconst_1表示将int类型1推送至栈顶 (inconst_m1 ~inconst_5)。

anewarray 表示创建一个引用类型(如类,接口,数组)的数组,并将其引用值推至栈顶。

newarray 表示创建一个指定的原始类型(如int,float,char等)的数组,并将其引用值推至栈顶。

类的初始化规则 /* * 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中, 这是在程序运行时,会导致主动使用这个常量所在的类,显然就会导致这个类被初始化。 */ public class MyTest3{ public static void main(String[] args){ System.out.println(MyParent3.str); } } class MyParent3{ public static final String str = UUID.randomUUID().toString(); static { System.out.println("Myparent3 static block"); // 这一行能输出吗?会 } } 为什么第二个例子不会输出,第三个例子就输出了呢? 因为第三个例子的值,是只有当运行期才会被确定的值。而第二个例子的值,是编译时就能被确定的值。 public class MyTest4{ public static void main(String[] args){ MyParent4 myParent4 = new MyParent4(); } } class MyParent4{ static { System.out.println("Myparent4 static block"); // 这一行能输出吗?会 } } 因为MyParent4 myParent4 = new MyParent4(); 属于主动使用的第一条,类的使用。 /** 对于数组实例来说,其类型是由JVM在运行期间动态生成的,表示为 [Lcom.erwa.MyTest4 这种形式,动态生成的类型,其父类型就是Object */ public class MyTest4{ public static void main(String[] args){ MyParent4[] myParent4s = new MyParent4[1]; System.out.println(myParent4s.getClass()); System.out.println(myParent4s.getClass().getSuperclass()); } } class MyParent4{ static { System.out.println("Myparent4 static block"); // 这一行能输出吗?不会 } } 因为 MyParent4[] myParent4s = new MyParent4[1]; 并不属于主动使用的方式。 > Task :MyTest4.main()输出结果为: class [Lcom.erwa.jvm.MyParent4; class java.lang.Object int[] ints = new int[1]; System.out.println(ints.getClass()); boolean[] booleans = new boolean[1]; System.out.println(booleans.getClass()); short[] shorts = new short[1]; System.out.println(shorts.getClass()); double[] doubles = new double[1]; System.out.println(doubles.getClass()); class [I class [Z class [S class [D 接口的初始化规则

接口本身的成员变量 都是 public static final 的

/** 当一个接口在初始化时,并不要求其父接口都完成了初始化。 只有在真正使用到父接口的时候(如 引用接口中所定义的常量时),才会初始化。 */ public class MyTest5{ public static void main(String[] args){ System.out.println(myParent5.b); } } interface MyParent5{ public static int a = 5; } interface MyChild5 extends MyParent5{ public static int b = 6; } /** class MyChild5 implements MyParent5{ public static int b = 6; } **/ 类的初始化顺序 /* 这个例子很好的阐述了类初始化的顺序问题。 */ public class MyTest6{ public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println("counter1:"+Singleton.counter1); System.out.println("counter2:"+Singleton.counter2); } } class Singleton{ public static int counter1 = 1; private static Singleton singleton= new Singleton(); private Singleton(){ counter1++; counter2++; // 准备阶段的重要意义 System.out.println(counter1); System.out.println(counter2); } public static int counter2 = 0; public static Singleton getInstance(){ return singleton; } } > Task :MyTest6.main() 2 1 counter1:2 counter2:0 类加载的加载顺序:

image-20200208221610181

image-20200208221655905

类加载的概念回顾

类的加载

类的加载的最终产品是位于内存中的Class对象

Class对象封装了类在方法去内的数据结构并且向Java程序员提供了访问方法区内的数据结构的接口。

有两种类型的类加载器

Java虚拟机自带的加载器

根类加载器(BootStrap)(BootClassLoader)

扩展类加载器(Extension)(ExtClassLoader)

系统(应用)类加载器(System)(AppClassLoader)

用户自定义的类加载器

Java.long.ClassLoader的子类

用户可以定制类的加载方式

类加载器并不需要等到某个类被“首次使用”时再加载它。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpszsp.html