从IDEA代码调试器的threads选项卡的一个细节谈如何学习编程

简介: 从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种解决办法;很多人解决一个问题要好几个小时甚至一两天,有些人能够快速找到问题的突破口。主要是基础是否扎实,逻辑是否严谨。
有些问题,很多人不屑,人最可怕的是不知道自己不知道,但是真得未必真得懂,真功夫往往体现在小问题上。我们要保持谦虚的态度去求知,去提升自己。

相关文章
|
2月前
|
XML 搜索推荐 JavaScript
Easy Code,IntelliJ IDEA中代码一键生成
Easy Code,IntelliJ IDEA中代码一键生成
Easy Code,IntelliJ IDEA中代码一键生成
|
4月前
IDEA常用插件之代码扫描SonarLint
IDEA常用插件之代码扫描SonarLint
143 0
|
2月前
|
人工智能 运维 Linux
一文了解IntelliJ IDEA如何使用git上传代码到GitHub(附常见问题解决方案)
一文了解IntelliJ IDEA如何使用git上传代码到GitHub(附常见问题解决方案)
92 0
|
4月前
|
Java 容器
idea中关于java的图形化界面编程awt_container容器中Button(按钮)上汉字是乱码或者小方框的解决方法
idea中关于java的图形化界面编程awt_container容器中Button(按钮)上汉字是乱码或者小方框的解决方法
46 0
|
20天前
|
前端开发 JavaScript 数据安全/隐私保护
idea代码review工具Code Review Helper使用介绍
CodeReview IDEA 插件是一款用于代码审查的工具,旨在解决在GitLab中查看整体业务逻辑的不便。该插件提供快速添加注释、行号旁的评审意见标记、双击跳转到代码、意见删除和修改、内容导出为Excel以及导入等功能。特别地,它支持离线和在线模式,离线模式下,审核者和开发者通过Excel文件交换评审意见;在线模式则通过服务端实现评审内容的上传和下载,简化文件传输。此外,该插件允许定制评审字段,并能与团队协作工具集成。通过这些特性,CodeReview IDEA 提高了代码审查的效率和便捷性。
69 2
|
4月前
|
应用服务中间件
【JavaWeb学习】—IDEA整合Tomcat服务器(十二)
【JavaWeb学习】—IDEA整合Tomcat服务器(十二)
|
1月前
IDEA代码行数统计插件Statistic对应idea版本以及安装后无法使用问题解决
IDEA代码行数统计插件Statistic对应idea版本以及安装后无法使用问题解决
22 0
|
1月前
|
JSON Java 测试技术
10 个解放双手的 IDEA插件,少些冤枉代码(第三弹)
10 个解放双手的 IDEA插件,少些冤枉代码(第三弹)
116 0
|
2月前
|
Java 关系型数据库 MySQL
idea自动生成代码插件EasyCode
idea自动生成代码插件EasyCode
|
2月前
|
安全 Shell 网络安全
Git学习---Git快速入门、Git基础使用、Git进阶使用、Git服务器使用(IDEA集成GitHub、Gitee、GitLab)、GitHub Desktop客户端
Git学习---Git快速入门、Git基础使用、Git进阶使用、Git服务器使用(IDEA集成GitHub、Gitee、GitLab)、GitHub Desktop客户端
131 0