一、背景
今天技術羣裏@段段同學提了一個很有意思的問題, IDEA的調試時, threads選項卡里,方法後面的 數字是啥意思??
有些同學說是代碼行數。但是我們發現有些代碼並不是代碼行數,而且還有 -1, 這是什麼鬼??
我們從這個很不起眼的問題,來講述如何分析問題,如何學習。
二、研究
2.1 猜測
猜測要有上下文,首先這是調試界面,顯然是給你提供調試的一些參考。
而數字的前面是一個 冒號,因此 這個數字應該代表 這個函數或者和這個函數有關係,最直接的理解就是源碼或者字節碼的函數行號。
但是 -1 解釋不通啊?
2.2 查閱資料
此時根據我們的風格,肯定要去查 JLS 和 JVMS (我認爲這兩個規範是JAVA工程師人手必備的,但是我相信甚至工作一兩年的人,都沒必備上,囧)。
https://docs.oracle.com/javase/specs/index.html
但是這顯然是 IDEA 提供的特性,殺雞焉用宰牛刀,先從IDEA自身下手。
2.3 IDEA 調試工具自身
當然最簡單直接的就是直接查IDEA使用文檔的調試器部分,應該可以找到答案。
https://www.jetbrains.com/help/idea/debug-tool-window-threads.html
https://www.jetbrains.com/help/idea/customize-threads-view.html
我們假裝沒看見,自己分析:
一般某個功能想修改或者進行一些額外的操作,就可以右鍵調出菜單,因此我們嘗試一下。
發現 有 Drop Frame (很重要,很好用,但是不在本文討論範圍之內), Export Threads , Add Stepping Filter.., Customize Threads View.. 四個選項。
眼前一亮,“Customize Threads View” 即 “自定義 Threads 視圖”,會不會有啥線索呢?
顯然 這個 “Show line number” 最可疑,因爲視圖中就這個選項是和數字相關。
因此我們可以去掉這個選項後觀察 threads 的顯示效果,發現的確之前的數字消失。
因此可以斷定,這個數字就是 函數的 line number (行號)。
另外我們恢復回去,雙擊對應的函數觀察行號和源碼的對應關係。
我們可以看到,在第三方 Jar 包 或本地代碼的行數上,該 行號對應的就是源碼的行號。
但是對於 JDK 的源碼,此 行號和 源碼的行號不對應,雙擊下圖中 62 對應的函數,跳轉到了 源碼中 27行,這是咋回事呢?
因此我們設想,會不會是字節碼中函數的行號呢?
因此需要 javap 反彙編看下源碼中的行號:
javap -c -l sun.reflect.NativeMethodAccessorImpl
Compiled from "NativeMethodAccessorImpl.java"
class sun.reflect.NativeMethodAccessorImpl extends sun.reflect.MethodAccessorImpl {
sun.reflect.NativeMethodAccessorImpl(java.lang.reflect.Method);
Code:
0: aload_0
1: invokespecial #1 // Method sun/reflect/MethodAccessorImpl."<init>":()V
4: aload_0
5: aload_1
6: putfield #2 // Field method:Ljava/lang/reflect/Method;
9: return
LineNumberTable:
line 39: 0
line 40: 4
line 41: 9
public java.lang.Object invoke(java.lang.Object, java.lang.Object[]) throws java.lang.IllegalArgumentException, java.lang.reflect.InvocationTargetException;
Code:
0: aload_0
1: dup
2: getfield #3 // Field numInvocations:I
5: iconst_1
6: iadd
7: dup_x1
8: putfield #3 // Field numInvocations:I
11: invokestatic #4 // Method sun/reflect/ReflectionFactory.inflationThreshold:()I
14: if_icmple 94
17: aload_0
18: getfield #2 // Field method:Ljava/lang/reflect/Method;
21: invokevirtual #5 // Method java/lang/reflect/Method.getDeclaringClass:()Ljava/lang/Class;
24: invokestatic #6 // Method sun/reflect/misc/ReflectUtil.isVMAnonymousClass:(Ljava/lang/Class;)Z
27: ifne 94
30: new #7 // class sun/reflect/MethodAccessorGenerator
33: dup
34: invokespecial #8 // Method sun/reflect/MethodAccessorGenerator."<init>":()V
37: aload_0
38: getfield #2 // Field method:Ljava/lang/reflect/Method;
41: invokevirtual #5 // Method java/lang/reflect/Method.getDeclaringClass:()Ljava/lang/Class;
44: aload_0
45: getfield #2 // Field method:Ljava/lang/reflect/Method;
48: invokevirtual #9 // Method java/lang/reflect/Method.getName:()Ljava/lang/String;
51: aload_0
52: getfield #2 // Field method:Ljava/lang/reflect/Method;
55: invokevirtual #10 // Method java/lang/reflect/Method.getParameterTypes:()[Ljava/lang/Class;
58: aload_0
59: getfield #2 // Field method:Ljava/lang/reflect/Method;
62: invokevirtual #11 // Method java/lang/reflect/Method.getReturnType:()Ljava/lang/Class;
65: aload_0
66: getfield #2 // Field method:Ljava/lang/reflect/Method;
69: invokevirtual #12 // Method java/lang/reflect/Method.getExceptionTypes:()[Ljava/lang/Class;
72: aload_0
73: getfield #2 // Field method:Ljava/lang/reflect/Method;
76: invokevirtual #13 // Method java/lang/reflect/Method.getModifiers:()I
79: invokevirtual #14 // Method sun/reflect/MethodAccessorGenerator.generateMethod:(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/Class;[Ljava/lang/Class;I)Lsun/reflect/MethodAccessor;
82: checkcast #15 // class sun/reflect/MethodAccessorImpl
85: astore_3
86: aload_0
87: getfield #16 // Field parent:Lsun/reflect/DelegatingMethodAccessorImpl;
90: aload_3
91: invokevirtual #17 // Method sun/reflect/DelegatingMethodAccessorImpl.setDelegate:(Lsun/reflect/MethodAccessorImpl;)V
94: aload_0
95: getfield #2 // Field method:Ljava/lang/reflect/Method;
98: aload_1
99: aload_2
100: invokestatic #18 // Method invoke0:(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
103: areturn
LineNumberTable:
line 49: 0
line 50: 21
line 51: 30
line 53: 41
line 54: 48
line 55: 55
line 56: 62
line 57: 69
line 58: 76
line 53: 79
line 59: 86
// 看這裏!
line 62: 94
void setParent(sun.reflect.DelegatingMethodAccessorImpl);
Code:
0: aload_0
1: aload_1
2: putfield #16 // Field parent:Lsun/reflect/DelegatingMethodAccessorImpl;
5: return
LineNumberTable:
line 66: 0
line 67: 5
}
反彙編之後一個很明顯的單詞映入眼簾:“LineNumberTable” 顯然,是 line number 的 表。
行號表中清晰地顯示, 62 行 對應上面的 code 中的 94。
而且從 94 代碼偏移 到 103 所表示的函數正是 27 行對應的源碼。
因此可以看出 JDK 中的代碼的行號對應的是反彙編後的行號而不是源碼中的行號。
那麼 -1 又代表着什麼呢?
雙擊 Invoke0 進入源碼,發現對應 jdk 中的 native 方法, 雙擊 execute 進入源碼,發現未知錯亂。
因此可以推測, -1 表示 native 函數 或者 未知的函數的位置(如 lambda表達式語法)。
此時回到 2.2 閱讀官方文檔部分
https://docs.oracle.com/javase/specs/index.html
找到 JVMS 對應的 “LineNumber” 部分章節:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.12
The
LineNumberTable
attribute is an optional variable-length attribute in theattributes
table of aCode
attribute (§4.7.3). It may be used by debuggers to determine which part of thecode
array corresponds to a given line number in the original source file.If multiple
LineNumberTable
attributes are present in theattributes
table of aCode
attribute, then they may appear in any order.There may be more than one
LineNumberTable
attribute per line of a source file in theattributes
table of aCode
attribute. That is,LineNumberTable
attributes may together represent a given line of a source file, and need not be one-to-one with source lines.// 省略部分
line_number_table[]
Each entry in the
line_number_table
array indicates that the line number in the original source file changes at a given point in thecode
array. Eachline_number_table
entry must contain the following two items:start_pc
The value of the
start_pc
item must indicate the index into thecode
array at which the code for a new line in the original source file begins.The value of
start_pc
must be less than the value of thecode_length
item of theCode
attribute of which thisLineNumberTable
is an attribute.line_number
The value of the
line_number
item must give the corresponding line number in the original source file.
It may be used by debuggers to determine which part of the code
array corresponds to a given line number in the original source file.
這句話一語中的:可能被調試器用來關聯 源碼中的 line number 和 code array 的對應關係。
也就是說:調試器可以通過 LineNumberTable 來關聯,源碼和反彙編後的代碼對應關係。
一個 LineNumberTable 的記錄表示 源文件中的行號 到 代碼起始位置的映射。
即 line 62 對應 反彙編後的 code 94 行。
三、思考
- 一個不起眼的問題可能隱藏着不少知識點,要多問幾個爲什麼,收穫完全不一樣。
- 大膽猜測,小心取證。很多人會把猜測當做事實,也有很多人遇到問題就直接問不思考。遇到問題先根據上下文和已有知識猜想最應該是怎樣,然後驗證。
- 要熟悉 IDEA, 對不熟悉的菜單要有一定的好奇心
- 官方的手冊可以說是最好的參考資料(包括Java 語言規範,JVM規範、Spring官方文檔等),可惜很多人其實並不重視!
- 要敢於走出舒適區,嘗試使用好的工具,比如javap反彙編,可以幫助你學的更多,更深入。但是很多工作幾年的人甚至都沒主動用過這個命令。調試代碼萬年只用單步,不會“回退”,不會多線程調試,不會注意左下角的調用棧等等。只有懂得方法多了,纔有更多的機會去嘗試各種突破口,而不是教條般地成爲百度俠。
- 排查問題的思路很重要,甚至超過答案本身。記住問題的答案只是一個信息,方法規律纔是能夠通用的知識。很多人遇到一個問題束手無策,也有一些人可以有N種解決辦法;很多人解決一個問題要好幾個小時甚至一兩天,有些人能夠快速找到問題的突破口。主要是基礎是否紮實,邏輯是否嚴謹。
- 有些問題,很多人不屑,人最可怕的是不知道自己不知道,但是真得未必真得懂,真功夫往往體現在小問題上。我們要保持謙虛的態度去求知,去提升自己。
-----------------------------------------------------
我在參見 CSDN 1024 程序員活動
如果我的博客對你有幫助,且有時間,歡迎瀏覽器後者微信掃碼,幫我點贊支持我: