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

相关文章
|
3月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
110 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
3月前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
52 6
|
3月前
|
Java Maven Spring
springboot学习一:idea社区版本创建springboot项目的三种方式(第三种为主)
这篇文章介绍了在IntelliJ IDEA社区版中创建Spring Boot项目的三种方法,特别强调了第三种方法的详细步骤。
2072 0
springboot学习一:idea社区版本创建springboot项目的三种方式(第三种为主)
|
3月前
|
Java 应用服务中间件 Maven
【终极解决方案】IDEA maven 项目修改代码不生效。
【终极解决方案】IDEA maven 项目修改代码不生效。
591 1
|
3月前
|
Java Linux 开发工具
IDEA中git提交前如何关闭code analysis以及开启格式化代码
【10月更文挑战第12天】本文介绍了在 IntelliJ IDEA 中关闭代码分析和开启代码格式化的步骤。关闭代码分析可通过取消默认启用检查或针对特定规则进行调整实现,同时可通过设置 VCS 静默模式在提交时跳过检查。开启代码格式化则需在 `Settings` 中配置 `Code Style` 规则,并通过创建 Git 钩子实现提交前自动格式化。
1093 3
|
4月前
|
Linux Windows
IDEA如何查看每一行代码的提交记录(人员,时间)
【9月更文挑战第24天】在IntelliJ IDEA中,可通过安装GitToolBox插件并利用其功能来便捷地查看每行代码的提交记录,包括提交者、时间和提交信息。具体操作为:首先安装GitToolBox插件,然后在代码编辑区域将鼠标悬停于目标代码行以查看简要信息,或使用快捷键打开“Version Control”窗口查看详细提交历史。
3038 2
|
5月前
|
Java 数据库连接 Spring
如何在IDEA中自定义模板、快速生成完整的代码?
这篇文章介绍了如何在IntelliJ IDEA中使用easycode插件自定义代码生成模板,以快速生成Spring Boot、MyBatis等项目中常见的Controller、Service、Dao、Mapper等组件的代码。
如何在IDEA中自定义模板、快速生成完整的代码?
|
5月前
|
XML 数据格式
IDEA 行注释设置,使其不从顶格开始,让其处于代码前开始
这篇文章提供了IntelliJ IDEA中如何设置行注释不从顶格开始,而是紧接在代码前面的方法,通过访问Settings中的Code Style选项进行调整,以改善代码注释的视觉效果。
|
5月前
|
开发工具 git
成功解决 IDEA 2020 版本 代码报错不提示的几种方案
这篇文章提供了几种解决IntelliJ IDEA 2020版本中代码报错不提示问题的方案,包括通过修改文件夹权限、暂存本地更改后进行git pull,以及在git pull后应用暂存的更改并提交代码到远程仓库的方法。
|
5月前
|
安全 开发工具 git
coding上创建项目、创建代码仓库、将IDEA中的代码提交到coding上的代码仓库、Git的下载、IDEA上配置git
这篇文章是关于如何在IDEA中配置Git、在Coding.net上创建项目和代码仓库,并将IDEA中的代码提交到远程代码仓库的详细教程,涵盖了Git安装、IDEA配置、项目创建、代码提交等步骤。
coding上创建项目、创建代码仓库、将IDEA中的代码提交到coding上的代码仓库、Git的下载、IDEA上配置git