Initialization初始化阶段
具体描述
类的初始化是类装载的最后一个阶段 ,如果前面的步骤没有问题,那么表示类可以顺利装载到系统中,此时,类才会开始执行Java字节码(即,到了初始化阶段,才真正开始执行类中定义的Java代码)初始化阶段的重要工作是执行类的初始化方法:()方法
该方法仅能由Java编译器生成并由JVM调用,程序开发者无法自定义一个同名方法,更无法直接在Java程序中调用该方法,虽然该方法也是由字节码指令所组成 它是由类静态成员的赋值语句以及static语句块合并产生的
说明
在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的总是在子类之前被调用,也就是说,父类的static块优先级高于子类------(由父及子,静态先行 ) Java编译器并不会为所有的类都产生初始化方法,有些类编译成字节码后,不会包含方法
一个类中并没有声明任何的类变量,也没有静态代码块时 一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时 一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式
public class InitializationTest2 { public static int a = 1 ; public static final int INT_CONSTANT = 10 ; public static final Integer INTEGER_CONSTANT1 = Integer . valueOf ( 100 ) ; public static Integer INTEGER_CONSTANT2 = Integer . valueOf ( 1000 ) ; public static final String s1 = "helloworld1" ; public static final String s0 = new String ( "helloworld0" ) ; public static final int NUM1 = 2 ; public static final int NUM2 = new Random ( ) . nextInt ( 10 ) ;
}
0 iconst_11 putstatic #2 < com/ chapter11/ InitializationTest2 . a> 4 bipush 100 6 invokestatic #3 < java/ lang/ Integer . valueOf> 9 putstatic #4 < com/ chapter11/ InitializationTest2 . INTEGER_CONSTANT1 >
12 sipush 1000
15 invokestatic #3 < java/ lang/ Integer . valueOf>
18 putstatic #5 < com/ chapter11/ InitializationTest2 . INTEGER_CONSTANT2 >
21 new #6 < java/ lang/ String >
24 dup
25 ldc #7 < helloworld0>
27 invokespecial #8 < java/ lang/ String . < init> >
30 putstatic #9 < com/ chapter11/ InitializationTest2 . s0>
33 new #10 < java/ util/ Random >
36 dup
37 invokespecial #11 < java/ util/ Random . < init> >
40 bipush 10
42 invokevirtual #12 < java/ util/ Random . nextInt>
45 putstatic #13 < com/ chapter11/ InitializationTest2 . NUM2 >
48 return
()线程安全性
对于()方法的调用,也就是类的初始化,虚拟机会在内部确保其多线程环境中的安全性 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程会执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕 因为函数()带锁线程是安全的 ,如果一个类的()方法中有耗时很长的操作,就可能造成多个线程阻塞,引发死锁,并且这种死锁很难发现,因为看起来没有可用的锁信息 如果之前的线程成功加载了类,则等在队列中的线程就没有机会再执行()方法了,那么当需要使用这个类时,虚拟机会直接返回它已经准备好的信息
class staticA { static { try { Thread . sleep ( 1000 ) ; } catch ( InterruptedException e) { } try { Class . forName ( "com.chapter11.staticB" ) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } System . out. println ( "StaticA init OK" ) ; }
}
class staticB { static { try { Thread . sleep ( 1000 ) ; } catch ( InterruptedException e) { } try { Class . forName ( "com.chapter11.staticA" ) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } System . out. println ( "StaticB init OK" ) ; }
}
public class StaticDeadLockMain extends Thread { private char flag; public StaticDeadLockMain ( char flag) { this . flag = flag; this . setName ( "Thread" + flag) ; } @Override public void run ( ) { try { Class . forName ( "com.chapter11.static" + flag) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } } public static void main ( String [ ] args) { StaticDeadLockMain sdlm1 = new StaticDeadLockMain ( 'A' ) ; StaticDeadLockMain sdlm2 = new StaticDeadLockMain ( 'B' ) ; sdlm1. start ( ) ; sdlm2. start ( ) ; }
}
主动使用VS被动使用
主动使用
Class只有在必须首次使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型,Java虚拟机规定,一个类或接口在初次使用前,必须进行初始化,这里指主动使用,主动使用有下面几种情况
当创建一个类的实例时,比如使用new关键字,通过反射,克隆,反序列化 当调用类的静态方法时,即当使用了字节码invokestatic指令
package com. chapter11 ; import org. junit. Test ; import java. io. * ;
class Order implements Serializable { static { System . out. println ( "Order类的初始化" ) ; } public static void method1 ( ) { System . out. println ( "order method1()... ..." ) ; }
}
public class ActiveUse1 { public static void main ( String [ ] args) { Order order = new Order ( ) ; } @Test public void test3 ( ) { Order . method1 ( ) ; } @Test public void testSerializable ( ) { ObjectOutputStream oos = null ; try { oos = new ObjectOutputStream ( new FileOutputStream ( "order.dat" ) ) ; oos. writeObject ( new Order ( ) ) ; } catch ( IOException e) { e. printStackTrace ( ) ; } finally { try { if ( oos != null ) { oos. close ( ) ; } } catch ( IOException e) { e. printStackTrace ( ) ; } } } @Test public void testDeSerializable ( ) { ObjectInputStream ois = null ; try { ois = new ObjectInputStream ( new FileInputStream ( "order.dat" ) ) ; Object o = ois. readObject ( ) ; if ( o instanceof Order ) { Order order = ( Order ) o; } } catch ( IOException e) { e. printStackTrace ( ) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } finally { try { if ( ois != null ) { ois. close ( ) ; } } catch ( IOException e) { e. printStackTrace ( ) ; } } }
}
当使用类,接口的静态字段时(finl特殊考虑),如getstatic或putstatic指令(对应访问变量,赋值变量)
class User { public static int num = 1 ; public static final int num1 = 2 ; public static final int num2 = new Random ( ) . nextInt ( 10 ) ; static { System . out. println ( "User类初始化" ) ; }
} interface CompareA { public static final Thread t = new Thread ( ) { { System . out. println ( "CompareA的初始化" ) ; } } ; public static final int NUM1 = 1 ; public static final int NUM2 = new Random ( ) . nextInt ( 10 ) ;
} public class ActiveUser2 { @Test public void test1 ( ) { System . out. println ( User . num) ; } @Test public void test2 ( ) { System . out. println ( User . num1) ; } @Test public void test3 ( ) { System . out. println ( User . num2) ; } @Test public void test4 ( ) { System . out. println ( CompareA . NUM1 ) ; } @Test public void test5 ( ) { System . out. println ( CompareA . NUM2 ) ; }
}
当使用java.lang.reflect包中的方法反射类的方法时,如Class.forName(“className”)
public class ActiveUse3 { @Test public void test1 ( ) { try { Class < ? > clazz = Class . forName ( "com.chapter11.Order" ) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } }
}
X当初始化子类时,发现父类没有初始化,需要触发其父类的初始化
@Test public void test2 ( ) { System . out. println ( Son . num) ; }
class Father { static { System . out. println ( "Father类的初始化过程" ) ; }
}
class Son extends Father { static { System . out. println ( "Son类的初始化过程" ) ; } public static int num = 1 ;
}
一个接口定义了default方法,那直接实现或间接实现该接口类的初始化,该接口要在其之前被初始化 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类 当初次调用MethodHandle实例时,初始化该MethodHandle指向的方法所在的类(涉及解析REF_getStatic,REF_putStatic,REF_invokeStatic方法句柄对应的类)
针对X项的补充,当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但这条规则不适用于接口
在初始化一个类时,并不会先初始化它所实现的接口 在初始化一个接口时,并不会先初始化它的父接口
@Test public void test3 ( ) { System . out. println ( Son . num) ; } @Test public void test4 ( ) { System . out. println ( compareC. num1) ; }
}
class Father { static { System . out. println ( "Father类的初始化过程" ) ; }
}
class Son extends Father implements compareB{ static { System . out. println ( "Son类的初始化过程" ) ; } public static int num = 1 ;
}
interface compareB { public static final Thread t = new Thread ( ) { { System . out. println ( "CompareB的初始化" ) ; } } ;
}
interface compareC extends compareB { public static final Thread t = new Thread ( ) { { System . out. println ( "CompareC的初始化" ) ; } } ; int num1 = new Random ( ) . nextInt ( 10 ) ;
}
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化,只有当程序员首次使用特定接口的静态字段时,才会导致该接口的初始化
@Test public void test5 ( ) { System . out. println ( Son . num) ; }
class Father { static { System . out. println ( "Father类的初始化过程" ) ; }
}
class Son extends Father implements compareB{ static { System . out. println ( "Son类的初始化过程" ) ; } public static int num = 1 ;
}
interface compareB { public static final Thread t = new Thread ( ) { { System . out. println ( "CompareB的初始化" ) ; } } ; public default void method1 ( ) { System . out. println ( "compareB--method1" ) ; }
}
Father 类的初始化过程
CompareB 的初始化
Son 类的初始化过程
1
针对JDK7,JVM启动的时候通过引导类加载器加载一个初始类,这个类在调用public static void main(String[] args)方法之前被链接和初始化,这个方法的执行将依次导致所需的类的加载,链接和初始化。
被动使用
被动使用不会引起类的初始化,并不是代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化
当访问一个静态字段时,只有真正声明这个字段的类才会被初始化
当通过子类引用父类的静态变量,不会导致此类的初始化
package com. chapter11 ; import org. junit. Test ;
class Parent { static { System . out. println ( "Parent初始化" ) ; } public static int num = 1 ;
}
class Child extends Parent { static { System . out. println ( "Child初始化" ) ; }
}
public class PassiveUse1 { @Test public void test1 ( ) { System . out. println ( Child . num) ; } @Test public void test2 ( ) { Parent [ ] parents = new Parent [ 10 ] ; } @Test public void test3 ( ) { Parent [ ] parents = new Parent [ 10 ] ; parents[ 0 ] = new Parent ( ) ; }
}
引用常量不会触发此类或接口的初始化,因为常量在链接阶段就已经被显示赋值了
class Person { static { System . out. println ( "Person类的初始化" ) ; } public final static int NUM = 1 ; public final static int NUM1 = new Random ( ) . nextInt ( 10 ) ;
}
public class PassiveUse2 { @Test public void test1 ( ) { System . out. println ( Person . NUM ) ; } @Test public void test2 ( ) { System . out. println ( Person . NUM1 ) ; } @Test public void test3 ( ) { System . out. println ( SerialA . NUM1 ) ; } @Test public void test4 ( ) { System . out. println ( SerialA . NUM2 ) ; }
}
interface SerialA { public static final Thread t = new Thread ( ) { { System . out. println ( "SerialA的初始化" ) ; } } ; int NUM1 = 2 ; int NUM2 = new Random ( ) . nextInt ( 10 ) ;
}
调用ClassLoader类的loadClass()方法加载一个类,并不对类的主动使用,不会导致类的初始化
@Test public void test5 ( ) { try { Class < ? > clazz = ClassLoader . getSystemClassLoader ( ) . loadClass ( "com.chapter11.Person" ) ; } catch ( ClassNotFoundException e) { e. printStackTrace ( ) ; } }