日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Java字节码研究

發布時間:2023/11/27 生活经验 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java字节码研究 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關于怎么查看字節碼的五種方法參考本人另一篇文章《Java以及IDEA下查看字節碼的五種方法》

1.String和常連池

先上代碼:

public class TestApp {public static void main(String[] args) {String s1 = "abc";String s2 = new String("abc");String s3 = new String("abc");System.out.println(s2 == s1.intern());System.out.println(s2 == s3.intern());System.out.println(s1 == s3.intern());System.out.println(s3 == s3.intern());String s4 = "abcd";String s5 = new String("abcde");System.out.println(s4);System.out.println(s5.intern());}
}

輸出:

false
false
true
false
abcd
abcde

第一個知識點--String.intern:

參考:Java-String.intern的深入研究

Returns a canonical representation for the string object. A pool of strings, initially empty, is maintained privately by the class String. When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true. All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java? Language Specification.

上面是jdk源碼中對intern方法的詳細解釋。簡單來說就是intern用來返回常量池中的某字符串,如果常量池中已經存在該字符串,則直接返回常量池中該對象的引用。否則,在常量池中加入該對象,然后返回引用。

生成class文件

Classfile /D:/TestApp.classLast modified 2019-3-5; size 869 bytesMD5 checksum 9093744ea00ada929804a84661bb3119Compiled from "TestApp.java"
public class TestAppminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #12.#25        // java/lang/Object."<init>":()V#2 = String             #26            // abc#3 = Class              #27            // java/lang/String#4 = Methodref          #3.#28         // java/lang/String."<init>":(Ljava/lang/String;)V#5 = Fieldref           #29.#30        // java/lang/System.out:Ljava/io/PrintStream;#6 = Methodref          #3.#31         // java/lang/String.intern:()Ljava/lang/String;#7 = Methodref          #32.#33        // java/io/PrintStream.println:(Z)V#8 = String             #34            // abcd#9 = String             #35            // abcde#10 = Methodref          #32.#36        // java/io/PrintStream.println:(Ljava/lang/String;)V#11 = Class              #37            // TestApp#12 = Class              #38            // java/lang/Object#13 = Utf8               <init>#14 = Utf8               ()V#15 = Utf8               Code#16 = Utf8               LineNumberTable#17 = Utf8               main#18 = Utf8               ([Ljava/lang/String;)V#19 = Utf8               StackMapTable#20 = Class              #39            // "[Ljava/lang/String;"#21 = Class              #27            // java/lang/String#22 = Class              #40            // java/io/PrintStream#23 = Utf8               SourceFile#24 = Utf8               TestApp.java#25 = NameAndType        #13:#14        // "<init>":()V#26 = Utf8               abc#27 = Utf8               java/lang/String#28 = NameAndType        #13:#41        // "<init>":(Ljava/lang/String;)V#29 = Class              #42            // java/lang/System#30 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;#31 = NameAndType        #45:#46        // intern:()Ljava/lang/String;#32 = Class              #40            // java/io/PrintStream#33 = NameAndType        #47:#48        // println:(Z)V#34 = Utf8               abcd#35 = Utf8               abcde#36 = NameAndType        #47:#41        // println:(Ljava/lang/String;)V#37 = Utf8               TestApp#38 = Utf8               java/lang/Object#39 = Utf8               [Ljava/lang/String;#40 = Utf8               java/io/PrintStream#41 = Utf8               (Ljava/lang/String;)V#42 = Utf8               java/lang/System#43 = Utf8               out#44 = Utf8               Ljava/io/PrintStream;#45 = Utf8               intern#46 = Utf8               ()Ljava/lang/String;#47 = Utf8               println#48 = Utf8               (Z)V
{public TestApp();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=6, args_size=10: ldc           #2                  // String abc              ///加載常量池中的第2項("abc")到棧中2: astore_1                                                     ///將0:中的引用賦值給第1個局部變量,即s1 = "abc"3: new           #3                  // class java/lang/String  ///生成String實例6: dup                                                          ///復制3:生成對象也就是s2的引用并壓入棧中7: ldc           #2                  // String abc              ///加載常量池中的第2項("abc")到棧中9: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V  ///調用常量池中的第4項,即java/lang/String."<init>"方法。12: astore_2                                                     ///將9:中的引用賦值給第2個局部變量,即s2 = new String("abc");13: new           #3                  // class java/lang/String  ///生成String實例16: dup                                                          ///復制13:生成對象的引用并壓入棧中17: ldc           #2                  // String abc              ///加載常量池中的第2項("abc")到棧中19: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V  ///調用常量池中的第4項,即java/lang/String."<init>"方法。22: astore_3                                                     ///將19:中的引用賦值給第3個局部變量,即s3 = new String("abc");23: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;  ///獲取指定類的靜態域,并將其值壓入棧頂26: aload_2                                                      ///把第2個本地變量也就是s2送到棧頂27: aload_1                                                      ///把第1個本地變量也就是s1送到棧頂28: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String;   ///對s1調用String.intern方法返回的是常連池對象的引用#231: if_acmpne     38                                             ///比較棧頂兩引用型數值,當結果不相等時跳轉  比較s2 == s1.intern()   6:和#2顯然不等34: iconst_1                                                     ///int型常量值1進棧 也就是true35: goto          39                                             ///跳轉到39:38: iconst_0                                                     ///int型常量值0進棧 也就是false39: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V42: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;45: aload_2                                                      ///把第2個本地變量也就是s2送到棧頂46: aload_3                                                      ///把第3個本地變量也就是s3送到棧頂47: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String; ///s3調用String.intern方法返回的是常連池對象的引用#250: if_acmpne     57                                            ///也就是比較s2 == s3.intern()  6:和#2顯然不等53: iconst_154: goto          5857: iconst_058: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V61: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;64: aload_1                                                      ///把第1個本地變量也就是s1送到棧頂65: aload_3                                                      ///把第3個本地變量也就是s3送到棧頂66: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String; //對s3調用String.intern方法返回的是常連池對象的引用#269: if_acmpne     76                                             ///也就是比較s1 == s3.intern()  #2和#2顯然相等72: iconst_173: goto          7776: iconst_077: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V80: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;83: aload_384: aload_385: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String;88: if_acmpne     95                                             ///也就是比較s3 == s3.intern()  13:和#2顯然不等91: iconst_192: goto          9695: iconst_096: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V99: ldc           #8                  // String abcd101: astore        4103: new           #3                  // class java/lang/String106: dup107: ldc           #9                  // String abcde109: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V112: astore        5114: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;117: aload         4119: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V122: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;125: aload         5127: invokevirtual #6                  // Method java/lang/String.intern:()Ljava/lang/String;130: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V133: returnLineNumberTable:line 5: 0line 6: 3line 7: 13line 9: 23line 10: 42line 11: 61line 12: 80line 14: 99line 15: 103line 16: 114line 17: 122line 18: 133StackMapTable: number_of_entries = 8frame_type = 255 /* full_frame */offset_delta = 38locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ]frame_type = 81 /* same_locals_1_stack_item */stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ]frame_type = 81 /* same_locals_1_stack_item */stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ]frame_type = 81 /* same_locals_1_stack_item */stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ]
}
SourceFile: "TestApp.java"

///是我加的注釋。可以參考:《通過反編譯深入理解Java String及intern》

JVM指令可以自行搜索。

?

關鍵就是這句:s1?==?s3.intern()

s1就是常量池的地址?也就是#2

而s3.intern()直接去找#2

#2==#2

所以是true!

比較s2?==?s1.intern()???6:和#2顯然不等???6:就是棧上的地址?自然和?常量池地址#2不等啊

總結這樣就行: 1.8下 ?

1. new字符串是會進常量池 ? ? ?

2.相等的字符串常量池自然相等 ?

3.常量池和棧地址自然不等 ??

2.String字符串拼接JVM自動優化為StringBuilder

說明:jdk1.8下,老版本不會轉

public static void main( String[] args ){User u=new User();u.setUserID("FX123");u.setUserName("張三");u.setUserAge(32);String aa="";aa+="Hello World!"+u.getUserID()+u.getUserName()+u.getUserAge();System.out.println(aa);}

所以在jdk1.8下完全沒必要自行拼接StringBuilder,編譯器生成字節碼的時候已經做了優化。

---------------

需要注意的是:使用Java 8進行字符串連接? ?談談JDK8中的字符串拼接

由于構建最終字符串的子字符串在編譯時已經已知了,在這種情況下Java編譯器才會進行如上的優化。這種優化稱為a static string concatenation optimization,自JDK5時就開始啟用。

那是否就能說明在JDK5以后,我們不再需要手動生成StringBuilder,通過+號也能達到同樣的性能?

我們嘗試下動態拼接字符串:

動態拼接字符串指的是僅在運行時才知道最終字符串的子字符串。比如在循環中增加字符串:

    public static void main(String[] args) {String result = "";for (int i = 0; i < 10; i++) {result += "some more data";}System.out.println(result);}

下面是jdk12的字節碼:?注意?InvokeDynamic?

// class version 56.0 (56)
// access flags 0x21
public class linuxstyle/blog/csdn/net/StringTest {// compiled from: StringTest.java// access flags 0x19public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup// access flags 0x1public <init>()VL0LINENUMBER 3 L0ALOAD 0INVOKESPECIAL java/lang/Object.<init> ()VRETURNL1LOCALVARIABLE this Llinuxstyle/blog/csdn/net/StringTest; L0 L1 0MAXSTACK = 1MAXLOCALS = 1// access flags 0x9public static main([Ljava/lang/String;)VL0LINENUMBER 5 L0LDC ""ASTORE 1L1LINENUMBER 6 L1ICONST_0ISTORE 2L2FRAME APPEND [java/lang/String I]ILOAD 2BIPUSH 10IF_ICMPGE L3L4LINENUMBER 7 L4ALOAD 1INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;)Ljava/lang/String; [// handle kind 0x6 : INVOKESTATICjava/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;// arguments:"\u0001some more data"]ASTORE 1L5LINENUMBER 6 L5IINC 2 1GOTO L2L3LINENUMBER 9 L3FRAME CHOP 1GETSTATIC java/lang/System.out : Ljava/io/PrintStream;ALOAD 1INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL6LINENUMBER 10 L6RETURNL7LOCALVARIABLE i I L2 L3 2LOCALVARIABLE args [Ljava/lang/String; L0 L7 0LOCALVARIABLE result Ljava/lang/String; L1 L7 1MAXSTACK = 2MAXLOCALS = 3
}

具體原因以及有人寫了很詳細了:

java字符串連接問題

《字符串連接你用+還是用StringBuilder》續

InvokeDynamic
可以看到JDK9之后生成的字節碼是比較簡潔的,只有一個 InvokeDynamic 指令,編譯器會給該類字節碼增加 invokedynamic 指令相關內容,包括方法句柄、引導方法、調用點、方法類型等等。它會調用 java.lang.invoke.StringConcatFactory 類中的makeConcatWithConstants方法,它有六種策略來處理字符串。如下代碼所示,有默認的策略,也可以通過java.lang.invoke.stringConcat啟動參數來修改策略。
有六種策略,前五種還是用StringBuilder實現,而默認的策略MH_INLINE_SIZED_EXACT,這種策略下是直接使用字節數組來操作,并且字節數組長度預先計算好,可以減少字符串復制操作。實現的核心是通過 MethodHandle 來實現 runtime,具體實現邏輯在MethodHandleInlineCopyStrategy.generate方法中。
?

StringBuilder.append鏈是否比字符串連接更有效?

什么是invokedynamic以及如何使用它?

如何在Java 9中實現String連接?

InvokeDynamic的第一口味

Java 9中的內容 - 性能,編譯器等

JSR 292:在Java?TM平臺上支持動態類型語言

?

3.多線程并發synchronized原理和局部變量和全局變量i++字節碼的差異

這里2個問題:

1.多線程并發i++需要加鎖,否則會有并發問題

2.i++作為局部變量其實是一步操作沒有分2步,因為局部變量不存在所謂的并發問題!

代碼

public class SynchronizedTest {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 100; i++) {new Thread(() -> {try {for (int j = 0; j < 1000; j++) {MyCount.addcount();}} catch (Exception e) {e.printStackTrace();}}).start();}sleep(10000);System.out.println("count:" + MyCount.getCount());}
}
public class MyCount {private static int count;public   static void addcount(){int k=0;synchronized(MyCount.class){k++;count++;}}public static Integer getCount() {return count;}
}

這里故意寫一個局部變量k,他的字節碼是iinc 0 by 1

synchronized的實現是靠monitorenter和monitorexit實現

參考:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter

?

4.多線程并發AtomicInteger原理

import java.util.concurrent.atomic.AtomicInteger;public class MyCount {private static int count;public static AtomicInteger value = new AtomicInteger();public   static void addcount(){value.incrementAndGet();}public static Integer getCount() {return count;}
}

字節碼非常簡潔。看java源碼

/*** Atomically increments by one the current value.** @return the updated value*/public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

CAS通過調用JNI的代碼實現的。JNI:Java?Native Interface為JAVA本地調用,允許java調用其他語言。

而compareAndSwapInt就是借助C來調用CPU底層指令實現的。下面從分析比較常用的CPU(intel x86)來解釋CAS的實現原理。可以看到這是個本地方法調用。這個本地方法在openjdk中依次調用的c++代碼為:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。

這個本地方法的最終實現在openjdk的如下位置:

openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(對應于windows操作系統,X86處理器)。下面是對應于intel x86處理器的源代碼的片段:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {// alternative for InterlockedCompareExchangeint mp = os::is_MP();//判斷是否是多處理器__asm {mov edx, destmov ecx, exchange_valuemov eax, compare_valueLOCK_IF_MP(mp)cmpxchg dword ptr [edx], ecx}
}// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \__asm je L0      \__asm _emit 0xF0 \__asm L0:
}

LOCK_IF_MP根據當前系統是否為多核處理器決定是否為cmpxchg指令添加lock前綴。

  1. 如果是多處理器,為cmpxchg指令添加lock前綴。
  2. 反之,就省略lock前綴。(單處理器會不需要lock前綴提供的內存屏障效果)

intel手冊對lock前綴的說明如下:

  1. 確保后續指令執行的原子性。
    在Pentium及之前的處理器中,帶有lock前綴的指令在執行期間會鎖住總線,使得其它處理器暫時無法通過總線訪問內存,很顯然,這個開銷很大。在新的處理器中,Intel使用緩存鎖定來保證指令執行的原子性,緩存鎖定將大大降低lock前綴指令的執行開銷。
  2. 禁止該指令與前面和后面的讀寫指令重排序。
  3. 把寫緩沖區的所有數據刷新到內存中。

上面的第2點和第3點所具有的內存屏障效果,保證了CAS同時具有volatile讀和volatile寫的內存語義。

CAS缺點

CAS存在一個很明顯的問題,即ABA問題

問題:如果變量V初次讀取的時候是A,并且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其他線程修改過了嗎?

如果在這段期間曾經被改成B,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。針對這種情況,java并發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它可以通過控制變量值的版本來保證CAS的正確性。

參考:https://www.cnblogs.com/Leo_wl/p/6899716.html

參考:https://www.jianshu.com/p/fb6e91b013cc

?

5.BTrace實現原理的初步分析

Btrace基于動態字節碼修改技術(Hotswap)來實現運行時java程序的跟蹤和替換。
Btrace的腳本是用純java編寫的,基于一套官方提供的annotation,使跟蹤邏輯實現起來異常簡單。

BTrace就是使用了java attach api附加agent.jar,然后使用腳本解析引擎+asm來重寫指定類的字節碼,再使用instrument實現對原有類的替換。借鑒這些,我們也完全可以實現自己的動態追蹤工具。

總體來說,BTrace是基于動態字節碼修改技術(Hotswap)來實現運行時java程序的跟蹤和替換。大體的原理可以用下面的公式描述:

Client(Java compile api + attach api) + Agent(腳本解析引擎 + ASM + JDK6 Instumentation) + Socket

參考《BTrace 原理淺析》本文是源碼分析

BTrace的入口類在:

https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Main.java

在其main方法中,可以看到起最終的核心邏輯是在:

https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Client.java

/*** Attach the BTrace client to the given Java process.* Loads BTrace agent on the target process if not loaded* already.*/public void attach(String pid, String sysCp, String bootCp) throws IOException {try {String agentPath = "/btrace-agent.jar";String tmp = Client.class.getClassLoader().getResource("com/sun/btrace").toString();tmp = tmp.substring(0, tmp.indexOf('!'));tmp = tmp.substring("jar:".length(), tmp.lastIndexOf('/'));agentPath = tmp + agentPath;agentPath = new File(new URI(agentPath)).getAbsolutePath();attach(pid, agentPath, sysCp, bootCp);} catch (RuntimeException re) {throw re;} catch (IOException ioexp) {throw ioexp;} catch (Exception exp) {throw new IOException(exp.getMessage());}}/*** Attach the BTrace client to the given Java process.* Loads BTrace agent on the target process if not loaded* already. Accepts the full path of the btrace agent jar.* Also, accepts system classpath and boot classpath optionally.*/public void attach(String pid, String agentPath, String sysCp, String bootCp) throws IOException {try {VirtualMachine vm = null;if (debug) {debugPrint("attaching to " + pid);}vm = VirtualMachine.attach(pid);if (debug) {debugPrint("checking port availability: " + port);}Properties serverVmProps = vm.getSystemProperties();int serverPort = Integer.parseInt(serverVmProps.getProperty("btrace.port", "-1"));if (serverPort != -1) {if (serverPort != port) {throw new IOException("Can not attach to PID " + pid + " on port " + port + ". There is already a BTrace server active on port " + serverPort + "!");}} else {if (!isPortAvailable(port)) {throw new IOException("Port " + port + " unavailable.");}}if (debug) {debugPrint("attached to " + pid);}if (debug) {debugPrint("loading " + agentPath);}String agentArgs = "port=" + port;if (statsdDef != null) {agentArgs += ",statsd=" + statsdDef;}if (debug) {agentArgs += ",debug=true";}if (trusted) {agentArgs += ",trusted=true";}if (dumpClasses) {agentArgs += ",dumpClasses=true";agentArgs += ",dumpDir=" + dumpDir;}if (trackRetransforms) {agentArgs += ",trackRetransforms=true";}if (bootCp != null) {agentArgs += ",bootClassPath=" + bootCp;}String toolsPath = getToolsJarPath(serverVmProps.getProperty("java.class.path"),serverVmProps.getProperty("java.home"));if (sysCp == null) {sysCp = toolsPath;} else {sysCp = sysCp + File.pathSeparator + toolsPath;}agentArgs += ",systemClassPath=" + sysCp;String cmdQueueLimit = System.getProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, null);if (cmdQueueLimit != null) {agentArgs += ",cmdQueueLimit=" + cmdQueueLimit;}agentArgs += ",probeDescPath=" + probeDescPath;if (debug) {debugPrint("agent args: " + agentArgs);}vm.loadAgent(agentPath, agentArgs);if (debug) {debugPrint("loaded " + agentPath);}} catch (RuntimeException re) {throw re;} catch (IOException ioexp) {throw ioexp;} catch (Exception exp) {throw new IOException(exp.getMessage());}}

---------------------

參考《BTrace實現原理的初步分析》本文是架構分析

實現原理
用一個簡單的公式來表述(從左往右的使用順序):
Sun Attach API + BTrace腳本解析引擎 + Objectweb ASM + JDK6 Instumentation

1,Sun Attach API是充當動態加載 agent 的角色。

2,BTrace解析引擎解析BTrace腳本。

3,解析完腳本后,Btrace會使用ASM將腳本里標注的類java.lang.Thread的字節碼重寫,植入跟蹤代碼或新的邏輯。
在上面那個例子中,Java.lang.Thread 這個類的字節碼被重寫了。并在start方法體尾部植入了 func 方法的調用。

4,利用instrumentation的retransformClasses,將原始字節碼替換掉。

替換后的字節碼是在新線程內才會生效的,老線程依舊用老的字節碼在執行。
替換的類原有的字段值是保持不變的。

局限性
BTrace的神通僅僅局限于只讀操作。不僅強制要求java腳本需要提供public static方法.而且,腳本里無法實例化對象,數組,不能拋異常或捕捉,不能有循環,內部類等等。針對一些特殊對象,BTrace也是無能為力的。比如java.lang.Integer,Array等。

不過話說回來,BTrace應付大部分應用場景還是綽綽有余的。

打破局限性約束
1,自己做instrumentation的類替換,繞過BTrace的安全檢查。
2,基于JVM TI自己寫工具,上面的局限性將蕩然無存,并且可以實現的功能會多很多。

----

《Instumentation及相關工具》

JVM Attach API ,JVM的 Attach有兩種方式:?
1. 指定javaagent參數 (premain方法)?
2. 運行時動態attach(agentmain方法)

進行jstack的時候,經常看到兩個線程Signal Dispatcher和 Attach Listener線程,這兩個線程是實現attach的關鍵所在,其中前者是在jvm啟動的時候就會創建的,后者只有接收過attach請求的時候vm才會創建,顧名思義,Signal Dispatcher是分發信號的, Attach Listener 是處理attach請求的,那么兩者有什么關系呢,當我們執行attach方法的時候,會向目標vm發出一個SIGQUIT 的信號,目標vm收到這個信號之后就會創建Attach Listener線程了.?
Attach機制說得簡單點就是提供A進程可以連上B進程(當然是java進程),創建socket進行通信,A通過發命令給B,B然后對命令進行截取從自己的vm中獲取信息發回給客戶端vm.?
Instrumentation的實現其實主要使用了load這個指令,它用來實現讓target vm動態加載agentlib,Instrumentation的實現在一個名為libinstrument.dylib的動態lib庫,linux下是libinstrument.so,它是基于jvmti接口實現的,因此在對其進行load的時候會創建一個agent實例….

具體調試方法:《基于Btrace的監控調試》

----------------------------

新增為什么main函數起的線程不能調用局部變量

代碼:

package com.company;public class Main {static int vvvvvvvvvvv=0;public static void main(String args[]){//主方法int iiiiiiiiiiiiii=0;System.out.println(">>>>>>>>>>1111111111111");new Thread(() -> {System.out.println(">>>>>>>>>>22222222222222");vvvvvvvvvvv++;System.out.println(vvvvvvvvvvv);System.out.println(">>>>>>>>>>3333333333");}).start();System.out.println(">>>>>>>>>>444444444444");new Thread() {public void run() {vvvvvvvvvvv++;vvvvvvvvvvv++;System.out.println(vvvvvvvvvvv);System.out.println(">>>>>>>>>>5555555555555");}}.start();System.out.println(">>>>>>>>>>666666666666666");}
}


?

注意要點中線程代碼塊才可以看到字節碼,不能在線程代碼塊之外!?

?完整字節碼:

// class version 52.0 (52)
// access flags 0x21
public class com/company/Main {// compiled from: Main.java// access flags 0x8static INNERCLASS com/company/Main$1 null null// access flags 0x19public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup// access flags 0x8static I vvvvvvvvvvv// access flags 0x1public <init>()VL0LINENUMBER 3 L0ALOAD 0INVOKESPECIAL java/lang/Object.<init> ()VRETURNL1LOCALVARIABLE this Lcom/company/Main; L0 L1 0MAXSTACK = 1MAXLOCALS = 1// access flags 0x9public static main([Ljava/lang/String;)VL0LINENUMBER 7 L0ICONST_0ISTORE 1L1LINENUMBER 8 L1GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC ">>>>>>>>>>1111111111111"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL2LINENUMBER 9 L2NEW java/lang/ThreadDUPINVOKEDYNAMIC run()Ljava/lang/Runnable; [// handle kind 0x6 : INVOKESTATICjava/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;// arguments:()V, // handle kind 0x6 : INVOKESTATICcom/company/Main.lambda$main$0()V, ()V]INVOKESPECIAL java/lang/Thread.<init> (Ljava/lang/Runnable;)VL3LINENUMBER 14 L3INVOKEVIRTUAL java/lang/Thread.start ()VL4LINENUMBER 15 L4GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC ">>>>>>>>>>444444444444"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL5LINENUMBER 16 L5NEW com/company/Main$1DUPINVOKESPECIAL com/company/Main$1.<init> ()VL6LINENUMBER 23 L6INVOKEVIRTUAL com/company/Main$1.start ()VL7LINENUMBER 24 L7GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC ">>>>>>>>>>666666666666666"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL8LINENUMBER 25 L8RETURNL9LOCALVARIABLE args [Ljava/lang/String; L0 L9 0LOCALVARIABLE iiiiiiiiiiiiii I L1 L9 1MAXSTACK = 3MAXLOCALS = 2// access flags 0x100Aprivate static synthetic lambda$main$0()VL0LINENUMBER 10 L0GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC ">>>>>>>>>>22222222222222"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL1LINENUMBER 11 L1GETSTATIC com/company/Main.vvvvvvvvvvv : IICONST_1IADDPUTSTATIC com/company/Main.vvvvvvvvvvv : IL2LINENUMBER 12 L2GETSTATIC java/lang/System.out : Ljava/io/PrintStream;GETSTATIC com/company/Main.vvvvvvvvvvv : IINVOKEVIRTUAL java/io/PrintStream.println (I)VL3LINENUMBER 13 L3GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC ">>>>>>>>>>3333333333"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL4LINENUMBER 14 L4RETURNMAXSTACK = 2MAXLOCALS = 0// access flags 0x8static <clinit>()VL0LINENUMBER 5 L0ICONST_0PUTSTATIC com/company/Main.vvvvvvvvvvv : IRETURNMAXSTACK = 1MAXLOCALS = 0
}
 0 getstatic #2 <com/company/Main.vvvvvvvvvvv>3 iconst_14 iadd5 putstatic #2 <com/company/Main.vvvvvvvvvvv>8 getstatic #2 <com/company/Main.vvvvvvvvvvv>
11 iconst_1
12 iadd
13 putstatic #2 <com/company/Main.vvvvvvvvvvv>
16 getstatic #3 <java/lang/System.out>
19 getstatic #2 <com/company/Main.vvvvvvvvvvv>
22 invokevirtual #4 <java/io/PrintStream.println>
25 getstatic #3 <java/lang/System.out>
28 ldc #5 <>>>>>>>>>>5555555555555>
30 invokevirtual #6 <java/io/PrintStream.println>
33 return

?

總結

以上是生活随笔為你收集整理的Java字节码研究的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。