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 类加载的加载顺序: 类加载的概念回顾类的加载
类的加载的最终产品是位于内存中的Class对象
Class对象封装了类在方法去内的数据结构并且向Java程序员提供了访问方法区内的数据结构的接口。
有两种类型的类加载器
Java虚拟机自带的加载器
根类加载器(BootStrap)(BootClassLoader)
扩展类加载器(Extension)(ExtClassLoader)
系统(应用)类加载器(System)(AppClassLoader)
用户自定义的类加载器
Java.long.ClassLoader的子类
用户可以定制类的加载方式
类加载器并不需要等到某个类被“首次使用”时再加载它。