Tomcat8优化--JVM字节码
Posted 关耳er
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat8优化--JVM字节码相关的知识,希望对你有一定的参考价值。
JVM字节码
前面我们通过tomcat本身的参数以及jvm的参数对tomcat做了优化,其实要想将应用程序跑的更快、效率更高,除了对tomcat容器以及jvm优化外,应用程序代码本身如果写的效率不高的,那么也是不行的,所以,对于程序本身的优化也就很重要了。
对于程序本身的优化,可以借鉴很多前辈们的经验,但是有些时候,在从源码角度方面分析的话,不好鉴别出哪个效率高,如对字符串拼接的操作,是直接“+”号拼接效率高还是使用StringBuilder效率高?
这个时候,就需要通过查看编译好的class文件中字节码,就可以找到答案。
我们都知道,java编写应用,需要先通过javac命令编译成class文件,再通过jvm执行,jvm执行时是需要将class文件中的字节码载入到jvm进行运行的。
1、通过javap命令查看class文件的字节码内容
1.1 创建一个简单的测试类
public class Test1 { public static void main(String[] args) { int a = 2; int b = 5; int c = b-a; System.out.println(c); } }
1.2 cmd 使用命令
javap -v Test1.class > Test1.txt
执行成功相应目录下生成Test1.txt文件:
1.3 查看Test1.txt文件 内容如下
Classfile /E:/accp/Y2/进阶内容/JVM/jvmTest/JvmTest/JVM_Project1/target/classes/com/zn/Test1.class Last modified 2020-3-10; size 563 bytes MD5 checksum 227f9972c01499bef690d9371d0b0e14 Compiled from "Test1.java" public class com.zn.Test1 minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#23 // java/lang/Object."<init>":()V #2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #26.#27 // java/io/PrintStream.println:(I)V #4 = Class #28 // com/zn/Test1 #5 = Class #29 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 LocalVariableTable #11 = Utf8 this #12 = Utf8 Lcom/zn/Test1; #13 = Utf8 main #14 = Utf8 ([Ljava/lang/String;)V #15 = Utf8 args #16 = Utf8 [Ljava/lang/String; #17 = Utf8 a #18 = Utf8 I #19 = Utf8 b #20 = Utf8 c #21 = Utf8 SourceFile #22 = Utf8 Test1.java #23 = NameAndType #6:#7 // "<init>":()V #24 = Class #30 // java/lang/System #25 = NameAndType #31:#32 // out:Ljava/io/PrintStream; #26 = Class #33 // java/io/PrintStream #27 = NameAndType #34:#35 // println:(I)V #28 = Utf8 com/zn/Test1 #29 = Utf8 java/lang/Object #30 = Utf8 java/lang/System #31 = Utf8 out #32 = Utf8 Ljava/io/PrintStream; #33 = Utf8 java/io/PrintStream #34 = Utf8 println #35 = Utf8 (I)V { public com.zn.Test1(); descriptor: ()V 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/zn/Test1; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: iconst_2 1: istore_1 2: iconst_5 3: istore_2 4: iload_2 5: iload_1 6: isub 7: istore_3 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: iload_3 12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 15: return LineNumberTable: line 5: 0 line 6: 2 line 7: 4 line 8: 8 line 9: 15 LocalVariableTable: Start Length Slot Name Signature 0 16 0 args [Ljava/lang/String; 2 14 1 a I 4 12 2 b I 8 8 3 c I } SourceFile: "Test1.java"
内容大致分为4个部分:
第一部分:显示了生成这个class的java源文件、版本信息、生成时间等。
第二部分:显示了该类中所涉及到常量池,共35个常量。
第三部分:显示该类的构造器,编译器自动插入的。
第四部分:显示了main方的信息。(这个是需要我们重点关注的)
2、常量池
官网文档:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140
Constant Type Value 说明
CONSTANT_Class 7 类或接口的符号引用
CONSTANT_Fieldref 9 字段的符号引用
CONSTANT_Methodref 10 类中方法的符号引用
CONSTANT_InterfaceMethodref 11 接口中方法的符号引用
CONSTANT_String 8 字符串类型常量
CONSTANT_Integer 3 整形常量
CONSTANT_Float 4 浮点型常量
CONSTANT_Long 5 长整型常量
CONSTANT_Double 6 双精度浮点型常量
CONSTANT_NameAndType 12 字段或方法的符号引用
CONSTANT_Utf8 1 UTF-8编码的字符串
CONSTANT_MethodHandle 15 表示方法句柄
CONSTANT_MethodType 16 标志方法类型
CONSTANT_InvokeDynamic 18 表示一个动态方法调用点
3、描述符
3.1 字段描述符
官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2
3.2 方法描述符
官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3
示例:
4、解读方法字节码
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V //方法描述,V表示该方法的返回值为void flags: ACC_PUBLIC, ACC_STATIC //方法修饰符,public,static的个数 Code: //stack=2,操作栈的大小为2,locals=4,本地变量表大小,args_size=1,参数的个数 stack=2, locals=4, args_size=1 0: iconst_2 //将数字2值压入擦作栈,位于栈的最上面 1: istore_1 //从操作栈中弹出一个元素(数字2),放入到本地变量表中,位于下标为1的位置(下标为0的是this) 2: iconst_5 //将数据5值压入操作栈,位于栈的最上面 3: istore_2 //操作栈中国弹出一个元素(5),放到本地变量表中,位于下标为2的位置 4: iload_2 //将本地变量表中下标为2的位置元素压入操作栈(5) 5: iload_1 //将本地变量表中下标为1的位置元素压入操作栈(2) 6: isub //操作栈中的2个数字相减 7: istore_3 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 通过#2号找到对应的常量,即可找到对应的引用 11: iload_3 12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 通过#3号找到对应的常量,即可找到对应的引用,进行方法调用 15: return LineNumberTable: line 5: 0 line 6: 2 line 7: 4 line 8: 8 line 9: 15 LocalVariableTable: //本地变量表 Start Length Slot Name Signature 0 16 0 args [Ljava/lang/String; 2 14 1 a I 4 12 2 b I 8 8 3 c I } SourceFile: "Test1.java"
4.1 图解
5、研究 i++ 与 ++i 的不同
i++表示,先返回再+1,++i表示,先+1再返回5.1 编写测试代码
package com.zn; public class Test2 { public static void main(String[] args) { new Test2().method01(); new Test2().method02(); } public void method01(){ int i=1; int a=i++; System.out.println(a); } public void method02(){ int i=1; int a=++i; System.out.println(a); } }
执行成功target目录下便会生成class文件:
5.2 cmd 使用命令
javap -v Test2.class > Test2.txt
执行成功相应目录下生成Test2.txt文件:
5.3 查看class字节码
Classfile /E:/accp/Y2/进阶内容/JVM/jvmTest/JvmTest/JVM_Project1/target/classes/com/zn/Test2.class Last modified 2020-3-10; size 779 bytes MD5 checksum f811b140c108be80c28048c85c5f9963 Compiled from "Test2.java" public class com.zn.Test2 minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #8.#27 // java/lang/Object."<init>":()V #2 = Class #28 // com/zn/Test2 #3 = Methodref #2.#27 // com/zn/Test2."<init>":()V #4 = Methodref #2.#29 // com/zn/Test2.method01:()V #5 = Methodref #2.#30 // com/zn/Test2.method02:()V #6 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream; #7 = Methodref #33.#34 // java/io/PrintStream.println:(I)V #8 = Class #35 // java/lang/Object #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 Lcom/zn/Test2; #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String; #20 = Utf8 method01 #21 = Utf8 i #22 = Utf8 I #23 = Utf8 a #24 = Utf8 method02 #25 = Utf8 SourceFile #26 = Utf8 Test2.java #27 = NameAndType #9:#10 // "<init>":()V #28 = Utf8 com/zn/Test2 #29 = NameAndType #20:#10 // method01:()V #30 = NameAndType #24:#10 // method02:()V #31 = Class #36 // java/lang/System #32 = NameAndType #37:#38 // out:Ljava/io/PrintStream; #33 = Class #39 // java/io/PrintStream #34 = NameAndType #40:#41 // println:(I)V #35 = Utf8 java/lang/Object #36 = Utf8 java/lang/System #37 = Utf8 out #38 = Utf8 Ljava/io/PrintStream; #39 = Utf8 java/io/PrintStream #40 = Utf8 println #41 = Utf8 (I)V { public com.zn.Test2(); descriptor: ()V 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/zn/Test2; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: new #2 // class com/zn/Test2 3: dup 4: invokespecial #3 // Method "<init>":()V 7: invokevirtual #4 // Method method01:()V 10: new #2 // class com/zn/Test2 13: dup 14: invokespecial #3 // Method "<init>":()V 17: invokevirtual #5 // Method method02:()V 20: return LineNumberTable: line 5: 0 line 6: 10 line 7: 20 LocalVariableTable: Start Length Slot Name Signature 0 21 0 args [Ljava/lang/String; public void method01(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: iconst_1 1: istore_1 2: iload_1 3: iinc 1, 1 6: istore_2 7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_2 11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V 14: return LineNumberTable: line 9: 0 line 10: 2 line 11: 7 line 12: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/zn/Test2; 2 13 1 i I 7 8 2 a I public void method02(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: iconst_1 1: istore_1 2: iinc 1, 1 5: iload_1 6: istore_2 7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_2 11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V 14: return LineNumberTable: line 14: 0 line 15: 2 line 16: 7 line 17: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/zn/Test2; 2 13 1 i I 7 8 2 a I } SourceFile: "Test2.java"
5.4 对比
i++:
public void method01(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: iconst_1 //将数字1压入到操作栈 1: istore_1 //将数字1从操作栈弹出,压入到本地变量表中,下标为1 2: iload_1 //将本地变量表中获取下标为1的数据,压入到操作栈中 3: iinc 1, 1 //将本地变量中的1,在+1 6: istore_2 //将数字1从操作栈弹出,压入到本地变量表中,下标为2 7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_2 //从本地变量表中获取下标为2的数据,压入到操作栈中 11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V 14: return LineNumberTable: line 9: 0 line 10: 2 line 11: 7 line 12: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/zn/Test2; 2 13 1 i I 7 8 2 a I
++i:
public void method02(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: iconst_1 //将数字1压入到操作栈中 1: istore_1 //将数字1从操作栈弹出,压入到本地变量表中,下标为1 2: iinc 1, 1 //将本地变量中的1,在+1 5: iload_1 //从本地变量表中获取下标为1的数据,压入到操作栈中 6: istore_2 //将数据2从操作栈弹出,压入到本地变量表中,下标为2 7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_2 //将本地变量表中获取下标为2的数据,压入到操作栈中 11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V 14: return LineNumberTable: line 14: 0 line 15: 2 line 16: 7 line 17: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/zn/Test2; 2 13 1 i I 7 8 2 a I
区别:
i++
只是在本地变量中对数字做了相加,并没有将数据压入到操作栈
将前面拿到的数字1,再次从操作栈中拿到,压入到本地变量中
++i
将本地变量中的数字做了相加,并且将数据压入到操作栈
将操作栈中的数据,再次压入到本地变量中
小结:可以通过查看字节码的方式对代码的底层做研究,探究其原理。
6、字符串拼接
字符串的拼接在开发过程中使用是非常频繁的,常用的方式有三种:
+号拼接: str+"456"
StringBuilder拼接
StringBuffer拼接
StringBuffer是保证线程安全的,效率是比较低的,我们更多的是使用场景是不会涉及到
线程安全的问题的,所以更多的时候会选择StringBuilder,效率会高一些。
下面探究StringBuilder和“+”号拼接,哪个效率高:
6.1 编写测试代码
package com.zn; public class Test3 { public static void main(String[] args) { new Test3().m1(); new Test3().m2(); } public void m1() { String s1 = "123"; String s2 = "456"; String s3 = s1 + s2; System.out.println(s3); } public void m2() { String s1 = "123"; String s2 = "456"; StringBuilder sb = new StringBuilder(); sb.append(s1); sb.append(s2); String s3 = sb.toString(); System.out.println(s3); } }
执行成功target目录下便会生成class文件:
6.2 cmd 使用命令
javap -v Test3.class > Test3.txt
执行成功相应目录下生成Test3.txt文件:
6.3 查看class字节码
Classfile /E:/accp/Y2/进阶内容/JVM/jvmTest/JvmTest/JVM_Project1/target/classes/com/zn/Test3.class Last modified 2020-3-10; size 1098 bytes MD5 checksum aedb0fe86c340564b9e93a5281e3069a Compiled from "Test3.java" public class com.zn.Test3 minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #14.#36 // java/lang/Object."<init>":()V #2 = Class #37 // com/zn/Test3 #3 = Methodref #2.#36 // com/zn/Test3."<init>":()V #4 = Methodref #2.#38 // com/zn/Test3.m1:()V #5 = Methodref #2.#39 // com/zn/Test3.m2:()V #6 = String #40 // 123 #7 = String #41 // 456 #8 = Class #42 // java/lang/StringBuilder #9 = Methodref #8.#36 // java/lang/StringBuilder."<init>":()V #10 = Methodref #8.#43 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #11 = Methodref #8.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String; #12 = Fieldref #45.#46 // java/lang/System.out:Ljava/io/PrintStream; #13 = Methodref #47.#48 // java/io/PrintStream.println:(Ljava/lang/String;)V #14 = Class #49 // java/lang/Object #15 = Utf8 <init> #16 = Utf8 ()V #17 = Utf8 Code #18 = Utf8 LineNumberTable #19 = Utf8 LocalVariableTable #20 = Utf8 this #21 = Utf8 Lcom/zn/Test3; #22 = Utf8 main #23 = Utf8 ([Ljava/lang/String;)V #24 = Utf8 args #25 = Utf8 [Ljava/lang/String; #26 = Utf8 m1 #27 = Utf8 s1 #28 = Utf8 Ljava/lang/String; #29 = Utf8 s2 #30 = Utf8 s3 #31 = Utf8 m2 #32 = Utf8 sb #33 = Utf8 Ljava/lang/StringBuilder; #34 = Utf8 SourceFile #35 = Utf8 Test3.java #36 = NameAndType #15:#16 // "<init>":()V #37 = Utf8 com/zn/Test3 #38 = NameAndType #26:#16 // m1:()V #39 = NameAndType #31:#16 // m2:()V #40 = Utf8 123 #41 = Utf8 456 #42 = Utf8 java/lang/StringBuilder #43 = NameAndType #50:#51 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #44 = NameAndType #52:#53 // toString:()Ljava/lang/String; #45 = Class #54 // java/lang/System #46 = NameAndType #55:#56 // out:Ljava/io/PrintStream; #47 = Class #57 // java/io/PrintStream #48 = NameAndType #58:#59 // println:(Ljava/lang/String;)V #49 = Utf8 java/lang/Object #50 = Utf8 append #51 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #52 = Utf8 toString #53 = Utf8 ()Ljava/lang/String; #54 = Utf8 java/lang/System #55 = Utf8 out #56 = Utf8 Ljava/io/PrintStream; #57 = Utf8 java/io/PrintStream #58 = Utf8 println #59 = Utf8 (Ljava/lang/String;)V { public com.zn.Test3(); descriptor: ()V 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/zn/Test3; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: new #2 // class com/zn/Test3 3: dup 4: invokespecial #3 // Method "<init>":()V 7: invokevirtual #4 // Method m1:()V 10: new #2 // class com/zn/Test3 13: dup 14: invokespecial #3 // Method "<init>":()V 17: invokevirtual #5 // Method m2:()V 20: return LineNumberTable: line 5: 0 line 6: 10 line 7: 20 LocalVariableTable: Start Length Slot Name Signature 0 21 0 args [Ljava/lang/String; public void m1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: ldc #6 // String 123 2: astore_1 3: ldc #7 // String 456 5: astore_2 6: new #8 // class java/lang/StringBuilder 9: dup 10: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V 13: aload_1 14: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: aload_2 18: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: astore_3 25: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 28: aload_3 29: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 32: return LineNumberTable: line 10: 0 line 11: 3 line 12: 6 line 13: 25 line 14: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 this Lcom/zn/Test3; 3 30 1 s1 Ljava/lang/String; 6 27 2 s2 Ljava/lang/String; 25 8 3 s3 Ljava/lang/String; public void m2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=5, args_size=1 0: ldc #6 // String 123 2: astore_1 3: ldc #7 // String 456 5: astore_2 6: new #8 // class java/lang/StringBuilder 9: dup 10: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V 13: astore_3 14: aload_3 15: aload_1 16: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: pop 20: aload_3 21: aload_2 22: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: pop 26: aload_3 27: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 30: astore 4 32: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 35: aload 4 37: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 40: return LineNumberTable: line 17: 0 line 18: 3 line 19: 6 line 20: 14 line 21: 20 line 22: 26 line 23: 32 line 24: 40 LocalVariableTable: Start Length Slot Name Signature 0 41 0 this Lcom/zn/Test3; 3 38 1 s1 Ljava/lang/String; 6 35 2 s2 Ljava/lang/String; 14 27 3 sb Ljava/lang/StringBuilder; 32 9 4 s3 Ljava/lang/String; } SourceFile: "Test3.java"
从字节码中可以看出,m1()方法源码中是使用+好拼接,但是字节码中也被编译成了StringBuilder方式;
所以可以得出结论,字符串拼接,+号和StringBuilder是相等的,效率一样;
6.4 测试代码2
package com.zn; public class Test4 { public static void main(String[] args) { new Test4().m1(); new Test4().m2(); } public void m1() { String str = ""; for (int i = 0; i < 5; i++) { str = str + i; } System.out.println(str); } public void m2() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.append(i); } System.out.println(sb.toString()); } }
6.5 生成txt查看字节码
javap -v Test4.class > Test4.txt
Classfile /E:/accp/Y2/进阶内容/JVM/jvmTest/JvmTest/JVM_Project1/target/classes/com/zn/Test4.class Last modified 2020-3-10; size 1176 bytes MD5 checksum dcc28dd685e81c8add6f9c826dd910a0 Compiled from "Test4.java" public class com.zn.Test4 minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #14.#39 // java/lang/Object."<init>":()V #2 = Class #40 // com/zn/Test4 #3 = Methodref #2.#39 // com/zn/Test4."<init>":()V #4 = Methodref #2.#41 // com/zn/Test4.m1:()V #5 = Methodref #2.#42 // com/zn/Test4.m2:()V以上是关于Tomcat8优化--JVM字节码的主要内容,如果未能解决你的问题,请参考以下文章