类的加载-连接-初始化过程都是在程序运行期间完成的,过程如下:
加载阶段:
/** * 类的加载:指得是将类的.class文件中的二进制数据加载读到内存中,将其放在内存模型的方法区内,并创建一个java.lang.Class对象封装类在方法区里的数据结构 *加载.class文件的方式: * 1.从本地磁盘中直接加载 * 2.通过网络下载的.class文件 * 3.从jar包中加载 * 4.动态代理生成的.class文件 *类加载器的分类: * java虚拟机自带的类加载器: * 根类加载器BootStrap 该加载器没有父类加载器,负责加载java.lang.*包下的所有类,它属于虚拟机实现的一部分,没有继承java.lang.ClassLoader类 * 拓展类加载器:Extension 该类加载器的父类为BootStrap,负责加载jdk安装目录的jre/lib/ext包下的类,如果将用户创建的jar放在该包下,也会被加载, * 该类加载器是纯java类,是java.lang.ClassLoader的子类 * 应用类(系统)加载器(System): 它的父类是拓展类加载器,加载环境变量classpath或者系统属性java.class.path所指定的目录下的类,是所有自定义类加载器的父类 * 程序输出: System.out.println(System.getProperty("java.class.path"))可以看到所有加载的类,包括当前应用的 * 用户自定义的类加载器: * java.lang.ClassLoader的子类 * jvm规范允许类加载器在预料某个类将会被使用时,预先加载它,如果预先加载的类不存在,或者有错误,类加载器必须在程序首次使用该类时,才报告错误LinkageError * 如果该类一直不会被使用,那么就类加载器就不会报告错误 * * 类加载双亲委派机制: * 程序请求类加载器classLoader去加载H类,classLoader会委派给其父类,其父类又会委派给父类的父类,一直到根类加载器BootStrap, * BootStrap在其加载路径下没找到该类,就下发给子类Extension加载器去加载,如果也没找到,就到应用类(系统)加载器,如果最终都没加载到,就 * 会报ClassNotFoundException;一般都是应用加载器(系统加载器)去加载,需要问题BootStrap和Extension,这2个父类,所以就叫双亲委派; * 最适合的叫法应该是父亲委派机制,每个类加载器都会先委派其父类加载器去加载 * * * */
连接阶段:
/** * 连接阶段: * 验证: 类文件的结构,语义检查,字节码验证,二进制兼容性的校验 * 准备: 为类的静态变量分配内存空间,并初始化默认值(不是真正用户赋予的值),如int初始化为0,对象初始化为null * 解析: 把类中的符号引用转换为直接引用,例如:A类引用了B类,编译成字节码后,A类起始并不知道B类在哪里,所以就使用了一个符号引用(符号可以是任何形式的字面量)表示B类, * 直接引用就是直接指向实际类的指针或便宜量,解析阶段就是要将类的符号引用转成直接引用 * */
/** * 初始化阶段: 为类的静态变量赋予真正的值: * java程序对类的使用分为: * 主动使用和被动使用; * 所有的java虚拟机实现必须在每个类或者接口被程序首次主动使用时,才会初始化他们 * 主动使用的情况: * 1.创建一个类的实例 * 2.访问某个类的静态变量,或对该类的静态变量赋值 * 3.调用该类静态方法 * 4.反射如(Class.forName("xxxxx)) * 5.初始化一个类的子类 * 6.java虚拟机启动时,被标为启动类的类,如Java Test类 * */
/** * * 类只有首次主动调用才会被初始化: * 如下例子演示: 访问一个类的静态属性,该类会被初始化,同时父类也会被初始化,当第二次访问时,不会再次初始化,证明了只有首次主动使用才会被初始化 * */ public class Test { public static void main(String[] args) { System.out.println(Child.str); //输出 parent init,child init hello System.out.println(Child.str); //hello } } class Parent{ public static String str2="parent"; static { System.out.println("parent init"); } } class Child extends Parent{ public static String str="hello"; static { System.out.println("child init"); } }
/** * * 如果将child的静态变量改成final修饰:下面结果将会输出hello,说明Child和parent都不会被初始化 * 原因是: public static final 修饰的变量为常量,在编译成字节码时,该常量已经加到调用该常量的类的常量池中了,也就是即时删除了Parent.class文件 * 和Child.class文件也不会报错 * */ public class Test { public static void main(String[] args) { System.out.println(Child.str); //输出hello } } class Parent{ public static String str2="parent"; static { System.out.println("parent init"); } } class Child extends Parent{ public static final String str="hello"; static { System.out.println("child init"); } }
/** * * 如果将child的静态变量改成final修饰,值为UUID生成的字符串 * 此时Parent和Child都被初始化了,原因是编译阶段没法确认Child.str的值,所在又符合了首次调用一个类的静态变量的情况; * 如果这时删除Parent或者Child的字节码文件,就会报错 * */ public class Test { public static void main(String[] args) { System.out.println(Child.str); //输出parent init child init e931a283-ffd8-4058-8fcc-a696f7fc284b } } class Parent{ public static String str2="parent"; static { System.out.println("parent init"); } } class Child extends Parent{ public static final String str= UUID.randomUUID().toString(); static { System.out.println("child init"); } }
/** * * 对数组的调用并不会导致数组元素的初始化,数组的真实类型是[L +元素的类型 * */ public class Test { public static void main(String[] args) { Child[] children=new Child[2]; System.out.println(children); //[Lcom.yang.jvm.Child;@7c53a9eb } } class Parent{ static { System.out.println("parent init"); } } class Child extends Parent { String str= UUID.randomUUID().toString(); static { System.out.println("Child init "); } }
package com.yang.jvm; /** * * 静态变量的初始化是按照类中的顺序初始化的: * 如下测试结果解析: * SingleTon instance = SingleTon.getINSTANCE();语句的调用,符合首次主动调用的调用一个类的静态方法的条件,这样类就会被初始化 * 此时执行public static int num1;的初始化 num1为0,之后执行private static SingleTon INSTANCE=new SingleTon();的初始化, * 该初始化调用了构造方法SingleTon(),此时num1=1,num2=1,因此会输出1,1之后初始化 public static int num2=3;所以num2=3 * 初始化完后,执行方法体 * System.out.println("是我先调用吗");打印"是我先调用吗" * return INSTANCE;此时这个方法体会将初始完后的对象返回,接着在main方法打印num1和num2的值就是1和3了, * 如果你将public static int num2=3;语句放到public static int num1;语句后面,结果又不一样了 * */ public class Test { public static void main(String[] args) { SingleTon instance = SingleTon.getINSTANCE(); System.out.println(SingleTon.num1); System.out.println(SingleTon.num2); //结果输出 1 1 "是我先调用吗" 1 3 } } class SingleTon{ public static int num1; private static SingleTon INSTANCE=new SingleTon(); public SingleTon() { num1++; num2++; System.out.println(num1); System.out.println(num2); } public static int num2=3; public static SingleTon getINSTANCE(){ System.out.println("是我先调用吗"); return INSTANCE; } }
package com.yang.jvm; /** * 接口的初始化,对于接口的初始化,并不会导致父接口的初始化, * 如果一个类初始化,也不会导致其实现的接口的初始化 * * * */ public class Test { public static void main(String[] args) { System.out.println(Child.str); System.out.println(Child2.str2); } } interface Parent{ public static final Thread thread=new Thread(){ { System.out.println("parent init"); } }; } interface Child extends Parent{ String str="hello"; } class Child2 implements Parent{ static String str2="ddd"; }
package com.yang.jvm; /** * * 如下测试证明: 一个类初始化,其父接口并不会被初始化和子类调用父类的成员变量并不会初始化子类,只会初始化该成员变量所属的类 */ public class Test { public static void main(String[] args) { //System.out.println(Child.str); //这个证明了,一个类初始化,其父接口并不会被初始化 System.out.println(Child.DEMO); //子类调用父类的成员变量并不会初始化子类,只会初始化该成员变量所属的类 } } interface Parent{ public static final Demo DEMO=new Demo(); } class Child implements Parent{ static String str="dd"; static { System.out.println("我有被初始化了吗?"); } } class Demo{ { System.out.println("开始创建了"); } }
/** * 通过在控制台输入命令: javap -c 字节码的路径,可以查看字节码的内容定义 * 字节码助记符: * ldc:表示字符串,float * iconst_1: -1到5的数字用iconst开头表示 * 给静态变量赋值: putstatic / getstatic /调用静态方法invokestatic * LDC2_W - Push long or double from constant pool * */ public class Test { public static void main(String[] args) { System.out.println(Child.str); //子类调用父类的成员变量并不会初始化子类,只会初始化该成员变量所属的类 } } class Child { static String str="dd"; static int a=1; static short b=12; static Long c=10L; static float d=12.2f; static double e=12.2f; static char f='a'; static byte g=23; static short h=255; }
//javap -c 查看字节码内容如下:其中每一个助记符都表示一个java类,例如putstatic的java类就是Putstatic,在java中可以找到该类
F:jvmdemo>javap -c build/classes/java/main/com/yang/jvm/Child.class Compiled from "Test.java" class com.yang.jvm.Child implements com.yang.jvm.Parent { static java.lang.String str; com.yang.jvm.Child(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return static {}; Code: 0: ldc #2 // String dd 2: putstatic #3 // Field str:Ljava/lang/String; 5: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #5 // String 我有被初始化了吗? 10: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: return } F:jvmdemo> F:jvmdemo>javap -c F:jvmdemouildclassesjavamaincomyangjvmChild.class Compiled from "Test.java" class com.yang.jvm.Child { static java.lang.String str; static int a; static short b; static java.lang.Long c; static float d; static double e; static char f; static byte g; static short h; com.yang.jvm.Child(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return static {}; Code: 0: ldc #2 // String dd 2: putstatic #3 // Field str:Ljava/lang/String; 5: iconst_1 6: putstatic #4 // Field a:I 9: bipush 12 11: putstatic #5 // Field b:S 14: ldc2_w #6 // long 10l 17: invokestatic #8 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; 20: putstatic #9 // Field c:Ljava/lang/Long; 23: ldc #10 // float 12.2f 25: putstatic #11 // Field d:F 28: ldc2_w #12 // double 12.199999809265137d 31: putstatic #14 // Field e:D 34: bipush 97 36: putstatic #15 // Field f:C 39: bipush 23 41: putstatic #16 // Field g:B 44: sipush 255 47: putstatic #17 // Field h:S 50: return }
package com.yang.jvm; /** * JVM命令的格式: * -XX:+<option> 表示开启option选项 * -XX:-<option> 表示关闭option选项 * -XX:<option>=<value> 表示将option的值设置为value * * //演示执行打印类加载信息命令-XX:+TraceClassLoading */ public class Test { public static void main(String[] args) { System.out.println(Child.str); //子类调用父类的成员变量并不会初始化子类,只会初始化该成员变量所属的类 } } class Child { static String str="dd"; static int a=1; static short b=12; static Long c=10L; static float d=12.2f; static double e=12.2f; static char f='a'; static byte g=23; static short h=255; }
jdk自带的JVM监控管理软件:通过在控制台输入命令jconsole 或者jvisualvm可以打开监控界面:
jvisualvm同理执行:其功能更加强大