微信公众号: 51码农网(www.51manong.com)
欢迎关注 如果觉得对你有帮助的话。没有帮助也没关系。
通过这篇文章,我们需要解决以下几个问题:
1public static int value=123; 2public static final value =123; 3上面两种写法,在类加载过程的准备阶段,初始值分别是多少? 复制代码
1public class Test { 2 public static void main(String[] args) { 3 String s = new String("1"); 4 s.intern(); 5 String s2 = "1"; 6 System.out.println(s == s2); 7 8 String s3 = new String("1") + new String("1"); 9 s3.intern(); 10 String s4 = "11"; 11 System.out.println(s3 == s4); 12 13 } 14} 复制代码
先说答案:(想一想是为什么?)
JDK1.6 false false
JDK1.7 false true
"补充点:"
验证阶段对于虚拟机的类加载机制来说,是一个非常重要,但不是一定必要(因为对程序运行期没有影响)的阶段,如果所运行的全部代码,都已经反复使用和验证过的话,那么在实施阶段就可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
1public static int value =123; 复制代码
那么变量在准备阶段过后的初始值为0而不是123,因为这个时候,还没有开始执行Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器
<clinit>()
方法之中,所以把value赋值为123的动作将在初始化阶段才会执行
数据类型 | 零值 | 数据类型 | 零值 |
---|---|---|---|
int | 0 | boolean | false |
long | oL | float | 0.0f |
short | (short)0 | double | 0.0d |
char | '/u0000' | reference | null |
byte | (byte)0 |
上面提到在 "通常情况" 下初始值是0值,那相对的会有一些 "特殊情况" :如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,如果上面的类变量定义变成:
1public static final int value =123; 复制代码
编译时候,javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在Class文件中,以CONSTANT_class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、等类型的常量出现。
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是
(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。
<clinit>()
方法的过程。 <clinit>()
方法
<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static(){})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量。编译器会报错。
1public class Test { 2 3 static{ 4 i=0; 5 //Cannot reference a field before it is defined 6 //提示"非法向前引用" 7 System.out.println(i); 8 } 9 static int i=1; 10} 复制代码
<clinit>()
方法与类的构造函数(或者说实例构造器 <init>()
方法不同,它不需要显示地调用父类的构造器,虚拟机会保证在子类的 <clinit>()
方法执行之前,父类的 <clinit>()
方法已经执行完毕。因此在虚拟机中第一个被执行的 <clinit>()
方法的类肯定是java.lang.Object。
由于父类的 <clinit>()
方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。查看以下代码
1public class Parent { 2 public static int A =1; 3 static{ 4 A=2; 5 System.out.println("Parent中的static执行了"); 6 } 7 8} 复制代码
1public class Children extends Parent { 2 public static int B=A; 3 static{ 4 System.out.println("child中的static执行了"); 5 } 6 7} 复制代码
1public class Test { 2 public static void main(String[] args) { 3 Children sub = new Children(); 4 System.out.println(sub.B); 5 } 6 7} 复制代码
1执行结果: 2Parent中的static执行了 3child中的static执行了 42 复制代码
在父类和子类上添加无参构造方法:
1public class Parent { 2 3 public Parent() { 4 super(); 5 System.out.println("Parent的无参构造执行了"); 6 } 7 public static int A =1; 8 static{ 9 A=2; 10 System.out.println("Parent中的static执行了"); 11 } 12 13} 复制代码
1public class Children extends Parent { 2 3 public Children() { 4 super(); 5 System.out.println("Children的无参构造执行了"); 6 } 7 public static int B=A; 8 static{ 9 System.out.println("child中的static执行了"); 10 } 11 12} 复制代码
1public class Test { 2 public static void main(String[] args) { 3 Children sub = new Children(); 4 System.out.println(sub.B); 5 } 6 7} 复制代码
1执行结果: 2Parent中的static执行了 3child中的static执行了 4Parent的无参构造执行了 5Children的无参构造执行了 62 复制代码
<clinit>()
方法对于类和接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 <clinit>()
方法
接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>()
方法。但接口与类不同的是,执行接口的 <clinit>()
方法不需要先执行父接口的 <clinit>()
方法。只有当父接口中定义了变量的使用时,父接口才会初始化,另外接口的实现类在初始化的时候也一样不会执行接口的 <clinit>()
方法。
接口中不能使用静态语句块
1public interface MyInterface { 2 3 //编译报错 接口MyInterface无法定义初始值设定项 4 static{ 5 System.out.println("MyInterface的static执行了"); 6 } 7 8} 复制代码
接口中仍然可以变量初始化赋值的操作
1public interface MyInterface { 2 //编译通过 正常 3 static int value = 123; 4} 复制代码
虚拟机会保证一个类的 <clinit>()
方法在多线程环境中被正确加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>()
方法完毕。如果在一个类的 <clinit>()
方法中有耗时很长的操作,就可能造成多个进程阻塞。(需要注意的是,其他线程虽然会被阻塞,但是如果执行 <clinit>()
方法的那条线程退出 <clinit>()
方法后,其他线程唤醒以后不会再进行 <clinit>()
方法,同一个类加载器下,一个类型只会初始化一次。)
1public class DeadLoopClass { 2 static{ 3 if(true){ 4 System.out.println(Thread.currentThread().getName()+",init DeadLoopClass"); 5 try { 6 Thread.sleep(2000); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 } 11 } 12 public static void main(String[] args) { 13 14 Runnable runAble = new Runnable() { 15 @Override 16 public void run() { 17 System.out.println(Thread.currentThread().getName()+",start"); 18 DeadLoopClass deadLoopClass = new DeadLoopClass(); 19 System.out.println(Thread.currentThread().getName()+",end"); 20 } 21 }; 22 Thread thread1 = new Thread(runAble); 23 Thread thread2 = new Thread(runAble); 24 thread1.start(); 25 thread2.start(); 26 } 27 28} 复制代码
1结果: 2main,init DeadLoopClass 3Thread-0,start 4Thread-0,end 5Thread-1,start 6Thread-1,end 复制代码
分析:
DeadLoopClass的初始化,并不是thread1和thread2执行了 <clinit>()
方法,而是main方法的线程去执行的了 <clinit>()
,当虚拟机启动时,需要指定一个要执行的主类,(包含main方法的那个类),虚拟机会先初始化这主类,这个时候,会有3个线程。main thread0 thread1 ,main去执行的这个类构造器 <clinit>()
方法,其他2个阻塞了,当main线程执行完毕,其他线程唤醒以后不会再进行 <clinit>()
方法,同一个类加载器下,一个类型只会初始化一次。
1public class DeadLoopClass { 2 static{ 3 if(true){ 4 System.out.println(Thread.currentThread().getName()+",init DeadLoopClass"); 5 while(true){ 6 7 } 8 } 9 } 10 public static void main(String[] args) { 11 Runnable runAble = new Runnable() { 12 @Override 13 public void run() { 14 System.out.println(Thread.currentThread().getName()+",start"); 15 DeadLoopClass deadLoopClass = new DeadLoopClass(); 16 System.out.println(Thread.currentThread().getName()+",end"); 17 } 18 }; 19 Thread thread1 = new Thread(runAble); 20 Thread thread2 = new Thread(runAble); 21 thread1.start(); 22 thread2.start(); 23 } 24 25} 复制代码
结果:
main,init DeadLoopClass
其他两个线程被一直阻塞了下去。
我是结尾
微信公众号:51码农网(www.51manong.com)
欢迎关注我,一起学习,一起进步!