第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > [中级01]java为什么能跨平台 而C\C++语言不能跨平台

[中级01]java为什么能跨平台 而C\C++语言不能跨平台

时间:2019-05-10 06:18:49

相关推荐

[中级01]java为什么能跨平台 而C\C++语言不能跨平台

编译后的成果物层面

同样的C\C++源文件文件,经过不同的计算机硬件(x86平台、arm、AMD)、不同的操作系统(Linux\mac\windows etc.)上的编译器编译后,生成了不同的机器码,是互不通用的。

而Java源码(.java)经过编译后,生成了class字节码文件,通过不同平台上的JVM(Java 虚拟机)都可以解释执行。JVM掩盖了计算机硬件和操作系统的差异,对class提供了统一的执行接口,这就是java为啥能跨平台的原因。

在面向对象设计原则中,有个原则叫“开闭原则”(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。java的跨平台性就是遵守了这一原则。它所使用的设计模式,是适配器模式。

#TODO: 如果深入理解这个问题,可能涉及到很多层面(编译原理、操作系统提供的API、操作系统内核提供的系统调用、CPU指令集),不同的操作系统中的函数库提供了不同的系统API(应用程序接口);不同的编译器和操作系统定义了不同的ABI(应用二进制接口);不同的CPU架构,有着不同的指令集。机器码就是指令集上指令对应的二进制(0和1组成的序列)表示。

以openjdk8为例

openjdk 是如何屏蔽操作系统和硬件的不同的呢?可以到jdk的源码中找到答案:

源码网址:jdk8u/jdk8u/hotspot: 00df30073cfa /

在源码目录的os和cpu中对应的代码,就是用来分别对操作系统和计算机硬件做屏蔽的

在学jvm的时候,我们很多人可能都见过下面这个图。下图中的Native Method Interface部分,就对应了上图中src目录下一部分的源码,所以如果想学好jvm,读源码是一个好方法。

首先我们来看看上面这个图的“Class File”这块,class文件是以什么样的形态被Class Loader SubSystem加载的。

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。

补充知识:String:字符串常量,字符串长度不可变。Java中String 是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。String str = "hello";存放在常量池中,String str=new String("hello");存放在堆上。StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用 StringBuffer,如果想转成 String 类型,可以调用 StringBuffer 的 toString() 方法。Java.lang.StringBuffer 线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。StringBuilder:字符串变量(非线程安全)。在内部 StringBuilder 对象被当作是一个包含字符序列的变长数组。基本原则:如果要操作少量的数据用 String ;单线程操作大量数据用StringBuilder ;多线程操作大量数据,用StringBuffer。

第二个层面:查看字节码

查看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"

在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

在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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。