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前缀。
- 如果是多处理器,为cmpxchg指令添加lock前缀。
- 反之,就省略lock前缀。(单处理器会不需要lock前缀提供的内存屏障效果)
intel手册对lock前缀的说明如下:
- 确保后续指令执行的原子性。
在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。 - 禁止该指令与前面和后面的读写指令重排序。
- 把写缓冲区的所有数据刷新到内存中。
上面的第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
相关文章:

在c语言中逗号的作用,关于c语言中的逗号运算符???
等下。。答错了。。还需要理解一下神马是逗号表达式。。我前面说的和uuyyhhjj与delta_charlie的意思一样,但其实我们都搞错了。你可以自己把我们的例子都运行一下,看看是不是这样。下面我感觉应该是我正确的理解。逗号表达式是所有运算符中优先级最低的&…

2018-2019-1 20165206 《信息安全系统设计基础》第4周学习总结
- 2018-2019-1 20165206 《信息安全系统设计基础》第4周学习总结 - 教材学习内容总结 程序员可见的状态:Y86-64程序中的每条指令都会读取或修改处理器状态的某些部分,这称为程序员可见状态。包括:程序寄存器、条件码、程序状态、程序计数器和…

PHP——图片上传
图片上传 Index.php文件代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Document</title> </head> <body><form action"upload2.php" method"p…

IDEA实用插件和技巧
《解决lambda expressions are not supported at this language level的问题》 《Intellij Idea 代码格式化/保存时自动格式化》 一、安装google-java-format preferences -> plugins -> Browse repositories… 搜索google-java-format 还有阿里的代码规范插件也不…

c语言将字母与数字分开存放,2017年计算机二级《C语言》考前提分试题及答案9...
二、程序填空题(共18分)、下列给定程序中,函数flm的功能是:将s所指字符串中的所有数字字符移到所有非数字字符之后,并保持数字字符串和非数字字符串原有的次序。例如,s所指的字符串为“def35adh3kjsdt7”,执行后结果为…

学术青年如何克服拖延症——5条技巧助你前进
雷锋网 AI 科技评论按:「我准备好了就开始」(或者说「拖延症」),以及「即便动起手来也觉得举步维艰」大概是每个现代人都逃不过的日常感受,不管是学习、在企业中工作,还是从事学术研究。我们可能都看过许多…
JDK源码研究Jstack,JMap,threaddump,dumpheap的原理
JDK最新bug和任务领取:https://bugs.openjdk.java.net/projects/JDK/issues 参加OpenJDK社区:https://bugs.openjdk.java.net/projects/JDK/issues openjdk源码地址: https://jdk.java.net/java-se-ri/8 https://download.java.net/openj…

C语言中regex_error,为什么这个C 11 std :: regex示例抛出一个regex_error异常?
参见英文答案 >Is gcc 4.8 or earlier buggy about regular expressions? 2尝试学习如何在C 11中使用新的std :: regex.但是我尝试的例子是抛出一个我不明白的regex_error异常.这是我的示例代码:#include #include int main…

如何删除mac通用二进制文件
通用二进制文件是什么? 计算机文件基本上分为二种:二进制文件和 ASCII(也称纯文本文件),图形文件及文字处理程序等计算机程序都属于二进制文件。这些文件含有特殊的格式及计算机代码。ASCII 则是可以用任何文字处理程序…

约瑟夫环 猴子选大王
<? /*** 猴子选大王:一群猴子排成一圈,按1,2,…,n依次编号。* 然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数,再数到第m只,在把它踢出去…,* 如此不停的进行下去,直…
Java使用字节码和汇编语言同步分析volatile,synchronized的底层实现
关于怎么查看字节码的五种方法参考本人另一篇文章《Java以及IDEA下查看字节码的五种方法》 查看汇编语言汇编码 说要看汇编还是很有必要的,因为有些地方比如加锁其实还是通过汇编实现的,只看字节码不能看出底层实现。 其实就是利用使用hsdis与jitwat…

云计算读书笔记(五)
Hadoop:Google云计算的开源实现 Hadoop是Apache开源组织的一个分布式计算机框架,可以在大量廉价的硬件设备组成的集群上运行应用程序,为应用程序提供一组稳定可靠的接口,旨在构建一个具有高可靠性和良好扩展性的分布式系统。 Hado…

自动跟随小车c语言,基于OpenCV的智能小车运动轨迹跟踪方法研究
摘要:随着人工智能技术的快速发展,智能小车开发受到越来越多研究者的关注,也已经成为一个重要的研究方向,而解决智能小车在路径规划中行驶的运动故障重要手段是对其进行的视频监控,但是智能小车的视频监控只能看到智能小车的行进状况而不进行相对应的处理,所以对智能…

JXJJOI2018_T1_market
题目描述 某天Lemon去超市买柠檬,他发现货架上有N个柠檬,每个柠檬都有一个重量Wi和价格Ci。 Lemon身上只带了S元钱,因此他想要买一个价格不超过S的柠檬回家,另外,他希望他买的那个柠檬的性价比尽量高。 性价比的定义是…

更好的Java虚拟机Zing: 更好的性能,无停顿,更快的启动
Zing虚拟机文档Understanding Java Garbage Collection(了解Java垃圾收集) 首先说明这个Zing是收费的,但是他也是优秀的,我觉得我们可以研究下他的一些思想对于怎么提高JVM,以及目前的JVM有什么缺陷是非常有帮助的。 中文版简介:…

c语言将水仙花数放入一维数组a中,全国计算机等级考试C语言考试程序设计题(13)...
在考生目录下,要求程序PROG.C的功能是:将所有的水仙花数保存到一维数组a中。(所谓水仙花数是指一个三位数,其各位数字立方和等于该数本身。例如:1531*1*15*5*53*3*3)#includevoid main(){void NONO( );//函数声明int a[10]{0},i…

[j2me]类似于OperaMini二级菜单界面演练[1]
拜朋友所赐,今日开始尝试如何绘制类似于Opera Mini的二级菜单,如下图所示:我自己的练习,还很幼稚,姑且记录如下:点击左软键,即可选中界面左下角的“选择”命令,二级菜单旋即弹出&…

宜人贷YEP技术、数据沉淀背后:金融科技迎来开放赋能时代
日前,“IFPI第十届金融科技决策者大会2018”在上海举办,宜人贷不仅入选了本届大会的“中国Fintech独角兽榜Top50”,推出的YEP共享平台也受到了众多金融机构的关注。从头部平台宜人贷全面开放金融科技能力来看,互联网金融行业历经混…

Redis源码和java jdk源码中hashcode的不同实现
一.redis实际上是使用了siphash 这个比较简单,我说的简单是指redis代码比较少不像jdk一样调用C代码调用栈非常深。 先看这个rehashing.c 主要就是dictKeyHash函数,需要调用dict.h头文件中定义的dictGenHashFunction #include "redis.h" #i…

android 7.0 短信监控,Android 7.0 监听网络变化的示例代码
Android7.0前,Android系统前网络切换时,会发广播,业务只要监听广播即可。public class NetChangeReceiver extends BroadcastReceiver {private static final String ANDROID_NET_CHANGE_ACTION "android.net.conn.CONNECTIVITY_CHANGE…

Mysql列类型-数值型
2019独角兽企业重金招聘Python工程师标准>>> 一、整数型: 1、取值范围: tinyint smallint mediumint int bigint 分别占用1、2、3、4、8个字节的存储空间 如:tinyint占用1个字节空间,它的取值范围&…

2018.10.22-dtoi1443奶牛逃亡(cowrun)
题目描述: Farmer John忘记修复他农场篱笆上的一个大洞,以至于篱笆围着的N(1< N <1,000)只奶牛从大洞中逃脱出来,并在农场里横冲直撞。每头在篱笆外的奶牛每分钟都将给他带来一美元的损失。FJ必须遍及每头奶牛、…

[转载]Linux 线程实现机制分析
自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性、效率。本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads 线程库的实现及其不足,描述了 Linux 社区是如何看待…
Java12和Jdk12安装以及OpenJdk12源码
文档: JDK 12文档:https://docs.oracle.com/en/java/javase/12/ 下载: OracleJDK12下载:https://www.oracle.com/technetwork/java/javase/downloads/jdk12-downloads-5295953.html csdn上我下好的,速度较快:https…
android 获取视频大小,Android 获取视频缩略图(获取视频每帧数据)的优化方案
速度对比左边的图片是通过方式1右边的图片是通过方式2speed.gif速度优化,效果拔群。在缩小2倍的Bitmap输出情况下使用MediaMetadataRetriever 抽帧的速度,每帧稳定在 300ms左右。使用MediaCodecImageReader 第一次抽帧。大概是200ms ,后续每帧则是50ms左…

Msql的DML、DDL、DCL的区别
DML(data manipulation language):它们是SELECT、UPDATE、INSERT、DELETE,这4条命令是用来对数据库里的数据进行操作的语言 DDL(data definition language):主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的…
JSR 133 Java内存模型以及并发编程的最权威论文汇总
Java内存模型 先看官方文档: https://docs.oracle.com/javase/specs/ JSR 133:Java TM内存模型和线程规范修订版:https://www.jcp.org/en/jsr/detail?id133 JSR:Java规范请求所有JSR的列表:https://jcp.org/en/jsr/…

ajax实现自动刷新页面实例
html部分:<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>ajax实现自动刷新</title> </head> <body onLoad"Autofresh()"> <p>现在的时间是:…

android aliasactivity作用,android activity-alias 的作用
activity-alias是android里为了重复使用Activity而设计的。当在Activity的onCreate()方法里,执行getIntent().getComponent().getClassName();得到的可能不是这个Activity的名字,有可能是别名的名字,例如:在AndroidMenifest.xml有…