從IDEA代碼調試器的threads選項卡的一個細節談如何學習編程

一、背景

今天技術羣裏@段段同學提了一個很有意思的問題, 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 the attributes table of a Code attribute (§4.7.3). 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.

If multiple LineNumberTable attributes are present in the attributes table of a Code attribute, then they may appear in any order.

There may be more than one LineNumberTable attribute per line of a source file in the attributes table of a Code 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 the code array. Each line_number_table entry must contain the following two items:

start_pc

The value of the start_pc item must indicate the index into the code 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 the code_length item of the Code attribute of which this LineNumberTable 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 程序員活動

如果我的博客對你有幫助,且有時間,歡迎瀏覽器後者微信掃碼,幫我點贊支持我:

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章