从二进制指令看包装类Integer在编译期处理优化及二进制指令代码解析
先看一段demo:
package com.onlyou; /** * Created by cd_huang on 2017/5/25. */ public class Test { public static void main(String args[]) { intTest(); } public static String intTest(){ int aa =100; Integer bb =100; System.out.println(aa==bb); Integer cc =100; System.out.println(bb==cc); int dd =1000; Integer ee =1000; System.out.println(dd==ee); Integer ff =1000; System.out.println(ee==ff); Integer gg =null; System.out.println(bb==gg); System.out.println(aa==gg); return null; } }
然后看运行结果:
true true true false false Exception in thread "main" java.lang.NullPointerException at com.onlyou.Test.intTest(Test.java:23) at com.onlyou.Test.main(Test.java:8)
第一个对比是int和Integer的对比,在编译期会处理成int100和Integer.intVal
ue获得int100,两个int100的对比,所以是true。
第二个对比是Integer和Integer的对比,所以纯粹是两个对象,如果指向同一个内存位置就会为true,这里理论上是false。但是编译期做了优化,Integer bb =100,其实等同于Integer bb =Integer.valueOf(100),这两种写法编译后的字节码是一样的。Integer.valueOf方法代码如下:
public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
代码对-127到127之间的Integer对象做了缓存,所以这里会是同一个对象,所以是true。
第三个对比是int和Integer的对比,是true的原因同第一个对比。
第四个对比是Integer和Integer的对比,是false的原因是1000不在-127到127之间,没有缓存,所以是两个对象。
第五个对比是Integer和Integer的对比,Integer100和null肯定是false。
第六个对比是int和Integer的对比,在编译期会处理成int和Integer.intVal来进行两个int的对比,Integer为空,所以这里直接报空指针异常。
所以,上面那段代码其实相当于下面这段代码。
package com.onlyou; /** * Created by cd_huang on 2017/5/25. */ public class Test { public static void main(String args[]) { intTest(); } public static String intTest(){ int aa =100; Integer bb =Integer.valueOf(100); System.out.println(aa==bb.intValue()); Integer cc =Integer.valueOf(100); System.out.println(bb==cc); int dd =1000; Integer ee =Integer.valueOf(1000); System.out.println(dd==ee.intValue()); Integer ff =Integer.valueOf(1000); System.out.println(ee==ff); Integer gg =null; System.out.println(bb==gg); System.out.println(aa==gg.intValue()); return null; } }
解释完了后我们来看编译后的指令是什么样子的。
C:\Users\hcd>javac c:\Test.java C:\Users\hcd>javap -v c:\Test.class Classfile /c:/Test.class Last modified 2017-5-25; size 1037 bytes MD5 checksum c36ad2112f8e55c60615146d0a116c8d Compiled from "Test.java" public class com.onlyou.Test SourceFile: "Test.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #8.#22 // java/lang/Object."<init>":()V #2 = Methodref #7.#23 // com/onlyou/Test.intTest:()Ljava/la ng/String; #3 = Methodref #24.#25 // java/lang/Integer.valueOf:(I)Ljava /lang/Integer; #4 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/Prin tStream; #5 = Methodref #24.#28 // java/lang/Integer.intValue:()I #6 = Methodref #29.#30 // java/io/PrintStream.println:(Z)V #7 = Class #31 // com/onlyou/Test #8 = Class #32 // java/lang/Object #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 main #14 = Utf8 ([Ljava/lang/String;)V #15 = Utf8 intTest #16 = Utf8 ()Ljava/lang/String; #17 = Utf8 StackMapTable #18 = Class #33 // java/lang/Integer #19 = Class #34 // java/io/PrintStream #20 = Utf8 SourceFile #21 = Utf8 Test.java #22 = NameAndType #9:#10 // "<init>":()V #23 = NameAndType #15:#16 // intTest:()Ljava/lang/String; #24 = Class #33 // java/lang/Integer #25 = NameAndType #35:#36 // valueOf:(I)Ljava/lang/Integer; #26 = Class #37 // java/lang/System #27 = NameAndType #38:#39 // out:Ljava/io/PrintStream; #28 = NameAndType #40:#41 // intValue:()I #29 = Class #34 // java/io/PrintStream #30 = NameAndType #42:#43 // println:(Z)V #31 = Utf8 com/onlyou/Test #32 = Utf8 java/lang/Object #33 = Utf8 java/lang/Integer #34 = Utf8 java/io/PrintStream #35 = Utf8 valueOf #36 = Utf8 (I)Ljava/lang/Integer; #37 = Utf8 java/lang/System #38 = Utf8 out #39 = Utf8 Ljava/io/PrintStream; #40 = Utf8 intValue #41 = Utf8 ()I #42 = Utf8 println #43 = Utf8 (Z)V { public com.onlyou.Test(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init> ":()V 4: return LineNumberTable: line 6: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: invokestatic #2 // Method intTest:()Ljava/lang/Str ing; 3: pop 4: return LineNumberTable: line 8: 0 line 9: 4 public static java.lang.String intTest(); flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=7, args_size=0 0: bipush 100 2: istore_0 3: bipush 100 5: invokestatic #3 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; 8: astore_1 9: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 12: iload_0 13: aload_1 14: invokevirtual #5 // Method java/lang/Integer.intVal ue:()I 17: if_icmpne 24 20: iconst_1 21: goto 25 24: iconst_0 25: invokevirtual #6 // Method java/io/PrintStream.prin tln:(Z)V 28: bipush 100 30: invokestatic #3 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; 33: astore_2 34: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 37: aload_1 38: aload_2 39: if_acmpne 46 42: iconst_1 43: goto 47 46: iconst_0 47: invokevirtual #6 // Method java/io/PrintStream.prin tln:(Z)V 50: sipush 1000 53: istore_3 54: sipush 1000 57: invokestatic #3 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; 60: astore 4 62: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 65: iload_3 66: aload 4 68: invokevirtual #5 // Method java/lang/Integer.intVal ue:()I 71: if_icmpne 78 74: iconst_1 75: goto 79 78: iconst_0 79: invokevirtual #6 // Method java/io/PrintStream.prin tln:(Z)V 82: sipush 1000 85: invokestatic #3 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; 88: astore 5 90: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 93: aload 4 95: aload 5 97: if_acmpne 104 100: iconst_1 101: goto 105 104: iconst_0 105: invokevirtual #6 // Method java/io/PrintStream.prin tln:(Z)V 108: aconst_null 109: astore 6 111: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 114: aload_1 115: aload 6 117: if_acmpne 124 120: iconst_1 121: goto 125 124: iconst_0 125: invokevirtual #6 // Method java/io/PrintStream.prin tln:(Z)V 128: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 131: iload_0 132: aload 6 134: invokevirtual #5 // Method java/lang/Integer.intVal ue:()I 137: if_icmpne 144 140: iconst_1 141: goto 145 144: iconst_0 145: invokevirtual #6 // Method java/io/PrintStream.prin tln:(Z)V 148: aconst_null 149: areturn LineNumberTable: line 11: 0 line 12: 3 line 13: 9 line 14: 28 line 15: 34 line 16: 50 line 17: 54 line 18: 62 line 19: 82 line 20: 90 line 21: 108 line 22: 111 line 23: 128 line 24: 148 StackMapTable: number_of_entries = 12 frame_type = 255 /* full_frame */ offset_delta = 24 locals = [ int, class java/lang/Integer ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ int, class java/lang/Integer ] stack = [ class java/io/PrintStream, int ] frame_type = 255 /* full_frame */ offset_delta = 20 locals = [ int, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ int, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream, int ] frame_type = 255 /* full_frame */ offset_delta = 30 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer ] stack = [ class java/io/PrintStream, int ] frame_type = 255 /* full_frame */ offset_delta = 24 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream, int ] frame_type = 255 /* full_frame */ offset_delta = 18 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream, int ] frame_type = 82 /* same_locals_1_stack_item */ stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ int, class java/lang/Integer, class java/lang/Integer, int, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ] stack = [ class java/io/PrintStream, int ] }
上面就是intTest()方法编译后的二进制指令。这边简单的解释下二进制指令怎么看。
首先简单介绍下jvm内存模型。
没花太多时间找到更合适的图片了。在我的理解里,更希望以线程私有还是线程共享来区分上图的每个部分,感觉会更清楚。上图转自http://blog.csdn.net/u012152619/article/details/46968883,对内存模型没什么了解的可以先看链接里的内容,当然,最好是看一下周志明老师的《深入理解Java虚拟机》,最经典的虚拟机学习书本。上图中方法区和堆是线程共享的,即每个线程都可以访问,所以存在线程安全的问题。而虚拟机栈,本地方法栈,程序计数器是线程独有的。程序的执行,调用一个方法时,就相当于把方法进栈,在方法执行结束后出栈,符合后进先出。
因为二进制指令主要是描述java代码的执行,所以主要关注虚拟机栈内的局部变量表和操作数栈。操作数栈是执行的真正工作区,是重中之重。二进制指令可以对照http://www.blogjava.net/DLevin/archive/2011/09/13/358497.html查看含义。注意每次操作数栈的内容使用完后都会出栈。下面是我给二进制指令加了些注释,以及写了在每一条指令执行完后,操作数栈和本地变量表的变化。
public static java.lang.String intTest(); flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=7, args_size=0(操作数栈长度为3,本地常量表的长度为7,因为是静态方法,所以方法的参数个位为0) 0: bipush 100 //valuebyte值带符号扩展成int值入栈。 (此时操作数栈内容为:100,null,null,局部变量表为:null,null,null,null,null,null,null) 2: istore_0 //将栈顶int类型值保存到局部变量0中。 (此时操作数栈内容为:null,null,null,局部变量表为:100,null,null,null,null,null,null) 3: bipush 100 //valuebyte值带符号扩展成int值入栈。 (此时操作数栈内容为:100,null,null,局部变量表为:100,null,null,null,null,null,null) 5: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; //调用静态方法Integer.valueOf (此时操作数栈内容为:Integer(100)的引用,null,null,局部变量表为:100,null,null,null,null,null,null) 8: astore_1 //将栈顶引用类型值保存到局部变量1中。 (此时操作数栈内容为:null,null,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 9: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; (此时操作数栈内容为:PrintStream的引用,null,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 12: iload_0 //从局部变量0中装载int类型值入栈。 (此时操作数栈内容为:100,PrintStream的引用,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 13: aload_1 //从局部变量1中装载引用类型值入栈。 (此时操作数栈内容为:Integer(100)的引用,100,PrintStream的引用,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 14: invokevirtual #9 // Method java/lang/Integer.intVal ue:()I (此时操作数栈内容为:100,100,PrintStream的引用,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 17: if_icmpne 24 //若栈顶两int类型值不相等则跳转。(跳转到24,即'24: iconst_0'这行) (此时操作数栈内容为:PrintStream的引用,null,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 20: iconst_1 //1(int)值入栈。 (此时操作数栈内容为:1,PrintStream的引用,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 21: goto 25 //无条件跳转到指定位置。(因为执行了20: iconst_1,所以跳过了24: iconst_0,直接跳到25: invokevirtual) 24: iconst_0 //0(int)值入栈。 (此时操作数栈内容为:0,PrintStream的引用,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 25: invokevirtual #5 // Method java/io/PrintStream.prin tln:(Z)V (此时操作数栈内容为:null,null,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 28: bipush 100 (此时操作数栈内容为:100,null,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 30: invokestatic #8 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; (此时操作数栈内容为:Integer(100)的引用,null,null,局部变量表为:100,Integer(100)的引用,null,null,null,null,null) 33: astore_2 (此时操作数栈内容为:null,null,null,局部变量表为:100,Integer(100)的引用,Integer(100)的引用,null,null,null,null) 34: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; (逻辑同上面。。这里就不再重述了) 37: aload_1 38: aload_2 39: if_acmpne 46 42: iconst_1 43: goto 47 46: iconst_0 47: invokevirtual #5 // Method java/io/PrintStream.prin tln:(Z)V 50: sipush 1000 53: istore_3 54: sipush 1000 57: invokestatic #8 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; 60: astore 4 62: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 65: iload_3 66: aload 4 68: invokevirtual #9 // Method java/lang/Integer.intVal ue:()I 71: if_icmpne 78 74: iconst_1 75: goto 79 78: iconst_0 79: invokevirtual #5 // Method java/io/PrintStream.prin tln:(Z)V 82: sipush 1000 85: invokestatic #8 // Method java/lang/Integer.valueO f:(I)Ljava/lang/Integer; 88: astore 5 90: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 93: aload 4 95: aload 5 97: if_acmpne 104 100: iconst_1 101: goto 105 104: iconst_0 105: invokevirtual #5 // Method java/io/PrintStream.prin tln:(Z)V 108: aconst_null 109: astore 6 111: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 114: aload_1 115: aload 6 117: if_acmpne 124 120: iconst_1 121: goto 125 124: iconst_0 125: invokevirtual #5 // Method java/io/PrintStream.prin tln:(Z)V 128: getstatic #4 // Field java/lang/System.out:Ljav a/io/PrintStream; 131: iload_0 132: aload 6 134: invokevirtual #9 // Method java/lang/Integer.intVal ue:()I 137: if_icmpne 144 140: iconst_1 141: goto 145 144: iconst_0 145: invokevirtual #5 // Method java/io/PrintStream.prin tln:(Z)V 148: aconst_null 149: areturn
文章中很多内容都只是个人的一些理解,如果有什么问题可以在评论里留言。欢迎交流。
上一篇: 使用dubbo进行消费者端调用导致内存溢出问题排查
下一篇: dubbo协议报文格式