Tomcat8优化--JVM字节码
Posted 慕容子月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat8优化--JVM字节码相关的知识,希望对你有一定的参考价值。
JVM字节码
通过javap命令查看class文件的字节码内容
创建一个简单的测试类
public class Test1 { public static void main(String[] args) { int a = 2; int b = 5; int c = b-a; System.out.println(c); } }
cmd打开窗口,使用命令
javap ‐v Test01.class > Test01.txt
查看Texst1.xml文件
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"
内容大致分为四个部分:
第一部分:显示了生成这个class的java源文件,版本信息,生成时间等;
第二部分:显示了该类中涉及到常量池,共35个;
第三部分:显示该类的构造器,编译器自动插入的;
第四部分:显示了main方的信息;
常量池
在JDK6.0及之前版本。String Pool里放的都是字符串常量;
在JDK1.7中,由于String #intern()发生了变化,因此String Pool中也可以存放于堆内的字符串对象的引用;
官方文档
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140
Constant Type value 说明
描述符
字段描述符
FieldTypeterm Type Interpretation
B byte signed byte
C char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16
方法描述符
官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3
实例
The method descriptor for the method:
Object m(int i, double d, Thread t) {...}
is:
(IDLjava/lang/Thread;)Ljava/lang/Object;
解读方法字节码
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: "Test01.java"
图解
研究i++和++i的不同
都知道,i++表示,先返回在+1;++i表示,先+1在返回;
测试代码:
public class Test02 { public static void main(String[] args) { new Test02().method01(); new Test02().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); } }
查看class字节码
javap -v Test02.class > Test02.txt
Last modified 2020-3-9; size 792 bytes MD5 checksum cfa985a5953fb3c7a88de6d9fa95bdac Compiled from "Test02.java" public class com.wn.Test.Test02 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/wn/Test/Test02 #3 = Methodref #2.#27 // com/wn/Test/Test02."<init>":()V #4 = Methodref #2.#29 // com/wn/Test/Test02.method01:()V #5 = Methodref #2.#30 // com/wn/Test/Test02.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/wn/Test/Test02; #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 Test02.java #27 = NameAndType #9:#10 // "<init>":()V #28 = Utf8 com/wn/Test/Test02 #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.wn.Test.Test02(); 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/wn/Test/Test02; 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/wn/Test/Test02 3: dup 4: invokespecial #3 // Method "<init>":()V 7: invokevirtual #4 // Method method01:()V 10: new #2 // class com/wn/Test/Test02 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/wn/Test/Test02; 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/wn/Test/Test02; 2 13 1 i I 7 8 2 a I } SourceFile: "Test02.java"
对比
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/wn/Test/Test02; 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/wn/Test/Test02; 2 13 1 i I 7 8 2 a I
区别
i++:
只是在本地变量中对数字做了相加,并没有将数据压入操作栈;
将前面拿到的数字1,再次从操作栈中拿到,压入到本地变量中;
++i:
将本地变量中的数字做了相加,并且将数据压入到操作栈;
将操作栈中的数据,再次压入到本地变量中;
小结:可以通过查看字节码的方式对代码的底层做研究,探究其原理;
字符串拼接
常见的字符串拼接方式
字符串的拼接在开发过程中使用是非常频繁的,常用的方式有三种:
+号拼接: str+"456"
StringBuilder拼接
StringBuffer拼接
StringBuffer是保证线程安全的,效率是比较低的,我们更多的是使用场景是不会涉及到
线程安全的问题的,所以更多的时候会选择StringBuilder,效率会高一些。
测试代码
public class Test03 { public static void main(String[] args) { new Test03().m1(); new Test03().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); } }
查看class字节码
javap -v Test03.class > Test03.txt
Last modified 2020-3-9; size 1111 bytes MD5 checksum 2f52301c461e860c7985882234cbdd5d Compiled from "Test03.java" public class com.wn.Test.Test03 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/wn/Test/Test03 #3 = Methodref #2.#36 // com/wn/Test/Test03."<init>":()V #4 = Methodref #2.#38 // com/wn/Test/Test03.m1:()V #5 = Methodref #2.#39 // com/wn/Test/Test03.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/wn/Test/Test03; #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 Test03.java #36 = NameAndType #15:#16 // "<init>":()V #37 = Utf8 com/wn/Test/Test03 #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/以上是关于Tomcat8优化--JVM字节码的主要内容,如果未能解决你的问题,请参考以下文章