当前位置:主页 > 查看内容

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

发布时间:2021-08-18 00:00| 位朋友查看

简介:一、背景 今天技术群里@段段同学提了一个很有意思的问题, IDEA的调试时, threads选项卡里,方法后面的 数字是啥意思?? 有些同学说是代码行数。但是我们发现有些代码并不是代码行数,而且还有 -1, 这是什么鬼?? 我们从这个很不起眼的问题,来讲述如何……

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


本文转自网络,原文链接:https://developer.aliyun.com/article/787273
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐