Java的class文件是什么
Class文件是jvm认识的一种字节码文件,里面的地址都是逻辑的地址。最后需要运行在操作系统中,操作系统只能识别真实的物理地址。此时需要动态链接(这个过程就是将逻辑地址变成物理地址),就是在运行时动态地绑定对象、对象地址。
此外,它还是一组以8位字节为基础单位的二进制流(容错性低,错一个字节则整个class文件不可用;节省空间\可以不用定义传输的格式,比如json,xml,而直接用二进制流传输数据),各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何的分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必需数据。
Class字节码文件只有两种数据类型:无符号数(u)和表(info)。
Class文件的规范可以在oracle的 jvm规格说明书的第4节(4. TheclassFile Format)中查看:
u4:4字节无符号数,u2类似
将一个.class文件用sublim打开,查看16进制形式,这个16进制形式按上面的ClassFile Structure对应解析例如:
package main.test;public class classtest {public static void main(String[] args) {int a = 11;int b = 22;int c = a+b;System.out.println(c);}}
生成class字节码后用sublim打开
cafe babe 0000 0034 0024 0a00 0500 17090018 0019 0a00 1a00 1b07 001c 0700 1d010006 3c69 6e69 743e 0100 0328 2956 01000443 6f64 6501 000f 4c69 6e65 4e75 6d626572 5461 626c 6501 0012 4c6f 6361 6c566172 6961 626c 6554 6162 6c65 0100 04746869 7301 0015 4c6d 6169 6e2f 7465 73742f63 6c61 7373 7465 7374 3b01 0004 6d61696e 0100 1628 5b4c 6a61 7661 2f6c 616e672f 5374 7269 6e67 3b29 5601 0004 61726773 0100 135b 4c6a 6176 612f 6c61 6e672f53 7472 696e 673b 0100 0161 0100 01490100 0162 0100 0163 0100 0a53 6f75 72636546 696c 6501 000e 636c 6173 7374 6573742e 6a61 7661 0c00 0600 0707 001e 0c001f00 0021 0c00 2200 2301 0013 6d61696e 2f74 6573 742f 636c 6173 7374 65737401 0010 6a61 7661 2f6c 616e 672f 4f626a65 6374 0100 106a 6176 612f 6c61 6e672f53 7973 7465 6d01 0003 6f75 7401 00154c6a 6176 612f 696f 2f50 7269 6e74 53747265 616d 3b01 0013 6a61 7661 2f69 6f2f5072 696e 7453 7472 6561 6d01 0007 7072696e 746c 6e01 0004 2849 2956 0021 00040005 0000 0000 0002 0001 0006 0007 00010008 0000 002f 0001 0001 0000 0005 2ab70001 b100 0000 0200 0900 0000 0600 01000000 0200 0a00 0000 0c00 0100 0000 05000b00 0c00 0000 0900 0d00 0e00 0100 08000000 6a00 0200 0400 0000 1210 0b3c 10163d1b 1c60 3eb2 0002 1db6 0003 b100 00000200 0900 0000 1600 0500 0000 0400 03000500 0600 0600 0a00 0700 1100 0800 0a000000 2a00 0400 0000 1200 0f00 1000 00000300 0f00 1100 1200 0100 0600 0c00 13001200 0200 0a00 0800 1400 1200 0300 01001500 0000 0200 16
则,
cafe babe (u4 magic;)
0000(u2minor_version;)
0034(u2 major_version;)
0024(u2 constant_pool_count;)
......
其余类似。
栗子:
string和stringbuffer的效率孰高孰低?
答:第一个层面:string有常量池,每次改变都会重新创建一个对象,stringbuffer是只创建一个对象,作出的改变都是在原来对象基础上进行append,大量的字符串拼接操作下,stringbuffer的效率要高于string。
第二个层面:查看字节码
查看Class字节码文件命令:
javap -verbose Test.class
格式:
存储:操作码+操作数 ,如istore_1iload_x: 从局部变量表中加载int类型的数据到操作数栈,lload fload dload aload常量加载到操作数栈:ldc ldc2 ldc_w bipush...对象创建、访问指令:new newarray XXXarray getfield pupfild getstatic putstatic控制转移指令:ifeq iflt ifle ifgt goto goto_w方法调用指令:invokevirtual(调用实例方法) invokestatic(调用静态方法) invokeintface invokeXXX
以下面的类为例(只看class1,和class2,main的部分只是为了程序运行时有个显示):
package main.test;class StringTest {public static void class1(){String str = "";for (int i=0;i<10;i++){str = str+"love,";}System.out.println(str);}public static void class2(){StringBuffer str = new StringBuffer();for (int i=0;i<10;i++){str.append("love,");}System.out.println(str);}public static void main(String[] args) {System.out.println("basket" + "ball");}}
执行#javap -verbose StringTest.class命令,查看class的字节码,
如下面代码所示,其实真正的字节码是上一节用sublim打开看到的十六进制的样子
用javap命令打开看到的其实翻译后的字节码,方便人类查看的,
下面这段翻译后的字节码文本比较长,我们只关注其中的class1,和class2
分析一下在循环使用时,string和stringbuffer的底层执行逻辑,然后判断孰优孰劣:
#javap -verbose StringTest.classLast modified 11月29日; size 1248 bytesMD5 checksum ab20ccd626637e7b03278358905d205eCompiled from "classtest.java"class main.test.StringTestminor version: 0major version: 52flags: (0x0020) ACC_SUPERthis_class: #15// main/test/StringTestsuper_class: #16 // java/lang/Objectinterfaces: 0, fields: 0, methods: 4, attributes: 1Constant pool:#1 = Methodref#16.#40 // java/lang/Object."<init>":()V#2 = String #41 //#3 = Class #42 // java/lang/StringBuilder#4 = Methodref#3.#40 // java/lang/StringBuilder."<init>":()V#5 = Methodref#3.#43 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#6 = String #44 // love,#7 = Methodref#3.#45 // java/lang/StringBuilder.toString:()Ljava/lang/String;#8 = Fieldref #46.#47 // java/lang/System.out:Ljava/io/PrintStream;#9 = Methodref#48.#49 // java/io/PrintStream.println:(Ljava/lang/String;)V#10 = Class #50 // java/lang/StringBuffer#11 = Methodref#10.#40 // java/lang/StringBuffer."<init>":()V#12 = Methodref#10.#51 // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;#13 = Methodref#48.#52 // java/io/PrintStream.println:(Ljava/lang/Object;)V#14 = String #53 // basketball#15 = Class #54 // main/test/StringTest#16 = Class #55 // java/lang/Object#17 = Utf8<init>#18 = Utf8()V#19 = Utf8Code#20 = Utf8LineNumberTable#21 = Utf8LocalVariableTable#22 = Utf8this#23 = Utf8Lmain/test/StringTest;#24 = Utf8class1#25 = Utf8i#26 = Utf8I#27 = Utf8str#28 = Utf8Ljava/lang/String;#29 = Utf8StackMapTable#30 = Class #56 // java/lang/String#31 = Utf8class2#32 = Utf8Ljava/lang/StringBuffer;#33 = Class #50 // java/lang/StringBuffer#34 = Utf8main#35 = Utf8([Ljava/lang/String;)V#36 = Utf8args#37 = Utf8[Ljava/lang/String;#38 = Utf8SourceFile#39 = Utf8classtest.java#40 = NameAndType #17:#18 // "<init>":()V#41 = Utf8#42 = Utf8java/lang/StringBuilder#43 = NameAndType #57:#58 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#44 = Utf8love,#45 = NameAndType #59:#60 // toString:()Ljava/lang/String;#46 = Class #61 // java/lang/System#47 = NameAndType #62:#63 // out:Ljava/io/PrintStream;#48 = Class #64 // java/io/PrintStream#49 = NameAndType #65:#66 // println:(Ljava/lang/String;)V#50 = Utf8java/lang/StringBuffer#51 = NameAndType #57:#67 // append:(Ljava/lang/String;)Ljava/lang/StringBuffer;#52 = NameAndType #65:#68 // println:(Ljava/lang/Object;)V#53 = Utf8basketball#54 = Utf8main/test/StringTest#55 = Utf8java/lang/Object#56 = Utf8java/lang/String#57 = Utf8append#58 = Utf8(Ljava/lang/String;)Ljava/lang/StringBuilder;#59 = Utf8toString#60 = Utf8()Ljava/lang/String;#61 = Utf8java/lang/System#62 = Utf8out#63 = Utf8Ljava/io/PrintStream;#64 = Utf8java/io/PrintStream#65 = Utf8println#66 = Utf8(Ljava/lang/String;)V#67 = Utf8(Ljava/lang/String;)Ljava/lang/StringBuffer;#68 = Utf8(Ljava/lang/Object;)V{main.test.StringTest();descriptor: ()Vflags: (0x0000)Code:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:Start Length Slot Name Signature0 50 this Lmain/test/StringTest;public static void class1();descriptor: ()Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=00: ldc #2 // String2: astore_03: iconst_04: istore_15: iload_16: bipush 108: if_icmpge3711: new #3 // class java/lang/StringBuilder14: dup15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V18: aload_019: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;22: ldc #6 // String love,24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;30: astore_031: iinc1, 134: goto537: getstatic#8 // Field java/lang/System.out:Ljava/io/PrintStream;40: aload_041: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V44: returnLineNumberTable:line 7: 0line 8: 3line 9: 11line 8: 31line 11: 37line 13: 44LocalVariableTable:Start Length Slot Name Signature5321i I3420 str Ljava/lang/String;StackMapTable: number_of_entries = 2frame_type = 253 /* append */offset_delta = 5locals = [ class java/lang/String, int ]frame_type = 250 /* chop */offset_delta = 31public static void class2();descriptor: ()Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=00: new #10 // class java/lang/StringBuffer3: dup4: invokespecial #11 // Method java/lang/StringBuffer."<init>":()V7: astore_08: iconst_09: istore_110: iload_111: bipush 1013: if_icmpge2916: aload_017: ldc #6 // String love,19: invokevirtual #12 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;22: pop23: iinc1, 126: goto1029: getstatic#8 // Field java/lang/System.out:Ljava/io/PrintStream;32: aload_033: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V36: returnLineNumberTable:line 16: 0line 17: 8line 18: 16line 17: 23line 20: 29line 22: 36LocalVariableTable:Start Length Slot Name Signature10191i I8290 str Ljava/lang/StringBuffer;StackMapTable: number_of_entries = 2frame_type = 253 /* append */offset_delta = 10locals = [ class java/lang/StringBuffer, int ]frame_type = 250 /* chop */offset_delta = 18public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic#8 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #14 // String basketball5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 25: 0line 26: 8LocalVariableTable:Start Length Slot Name Signature0 90 args [Ljava/lang/String;}SourceFile: "classtest.java"
1. 在class1中if_icmpge 和goto之间的就是源码的for循环部分,这里我们可以看到,每循环一次,中间都会调用一下new,new出一个stringBuilder的对象(string底层借助的是stringBuilder来实现的),循环多少次就要new多少次,可见开销是很大的
8:if_icmpge3711: new #3 // class java/lang/StringBuilder14: dup15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V18: aload_019: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;22: ldc #6 // String love,24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;30: astore_031: iinc1, 134: goto5
2. 在class2中,if_icmpge 和goto之间,只是调用的append操作,复用的是类开始的new的那个对象,由此可见,频繁操作时,stringBuffer要比string高效
13: if_icmpge2916: aload_017: ldc #6 // String love,19: invokevirtual #12 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;22: pop23: iinc1, 126: goto10
如果觉得《【中级02】Java Class字节码文件底层逻辑详解》对你有帮助,请点赞、收藏,并留下你的观点哦!