专栏首页养码场一件程序员必备武器的诞生

一件程序员必备武器的诞生

以下文章来源于码农翻身,作者码农翻身刘欣

01 引子

夜已深,但是Java第一代国王却无心睡眠,帝国刚刚建立,东边的C/C++王国虎视眈眈,随时准备把新生的王国扼杀在摇篮中。

今日GUI大臣上奏,说帝国子民抱怨运行速度慢,这一点Java国王也没有好办法,解释执行嘛,肯定比不上编译好的程序,不过Java国王已经下令去研发HotSpot了,等到儿子即位就会大有改观。

不过GUI大臣提出的另外一个问题的确让人发愁:帝国子民写出的程序调试困难, 大家得用最原始的System.out.println()来查看变量,定位错误。

这怎么能行?程序不能调试,相当于瘸了一条腿啊!这将严重影响新生Java帝国的找Bug事业。

02 调试的基础

第二天早朝,眼圈发黑的国王把JVM大臣怒斥了一顿,勒令他马上把调试这一块给搞好。

JVM大臣非常委屈:“陛下,当初我们在设计Class文件的字节码的时候,就考虑到了调试的需求,Java文件编译成class文件以后,其中有个叫做LineNumberTable的区域,它描述了Java源代码和字节码行号(字节码偏移量)之间的对应关系,有了它,我们才能加断点调试啊!”

他担心国王不明白是怎么回事,现场画了一张图。

国王没有心思去理解那些iload, iadd,istore是什么含义,但是他理解了源代码和字节码之间的对应关系,确实是在LineNumberTable中记录的。

源码的第13行 是int sum = x + y;对应的字节码行号是0 ~ 3。

源码中第14行是 return sum。对应的字节码行号是 4 ~ 5。

国王点头认可,问道:“那是不是可以做一个调试器了?”

JVM大臣:“臣正有此意,臣打算把Java的调试器叫做jdb。”

IO大臣听到JDB立刻跳了起来:“加(J)多(D)宝(B),你怎么不叫王老吉啊!”

JVM大臣蔑视地看了IO大臣一眼:“C王国有个调试器叫gdb, 我把它叫做jdb, Java Debugger, 别想歪了!”

国王说:“你这个JDB是命令行的吧?”

JVM大臣答道:“陛下明鉴, 臣这里只能弄个命令行的调试器,因为帝国的子民用的IDE都不一样,臣也没法给每个IDE都开发一个图形界面的调试器,这不是臣应该干的活。”

国王点头:“寡人理解,你的重点还是要放在HotSpot上,我们已经被C/C++嘲笑很久了,能不能翻身出这口恶气就靠你了。”

GUI大臣说:“陛下圣明,我们应该充分发挥我们Java帝国善于制定规范和协议的特长,搞一套关于调试的规范出来,这样,任何人/任何IDE都可以根据规范来开发一个调试器。”

国王:“爱卿之言甚合我意,GUI大臣,IO大臣,JVM大臣,你们三个通力合作,把这一套规范给制定出来!”

03 JVM接口

三位大臣不敢怠慢,一退朝就急忙赶到JVM大臣府上讨论这套规范该怎么制定。

JVM大臣率先发言:“诸位,我这里设置一个底线,那就是调试器和被调试的程序不要处于一个JVM中。”

GUI大臣表示不解:“为什么?”

“很简单,如果它们两个在一个JVM中,那被调试程序的独立性就不能保证了,可能会受到调试器的影响。举个极端的例子,调试器占据了很多Heap空间,导致被调试程序OOM了.....”

IO大臣:“那我们可以设计成C/S模式的,让它们之间通过socket通信怎么样?”

“如果这调试器和被调试程序都在一台机器上,用socket多少有点怪,我们也要支持共享内存的方式来通信。”

GUI大臣说:“如此看来, JVM老兄,你得提供接口啊,让调试器可以访问Java程序在运行时的状态,嗯,我觉得至少得有这些功能:

获取一个线程的状态, 挂起一个线程,让线程恢复执行, 设置一个线程,单步执行

获取线程的当前栈帧,调用栈帧,栈帧对应的方法名

获取变量的值, 设置变量的值

设置断点,清除断点

查看类的信息,方法,字段 等等

JVM大臣撇了一眼GUI大臣,心说这家伙是个内行啊,看来写过不少GUI的调试器,不过他也难不住我,我负责JVM,拿到这些Java程序运行时的信息还不是小菜一碟?

JVM大臣说:“这没问题,我可以把这些接口给细化了,形成规范,然后请一道圣旨,要求各个JVM的提供商都要实现这些接口。”

“不过,” JVM大臣接着说:“为了通用性和性能,我这里只能提供C语言的接口。嗯,这个接口就叫做JVM Tool Interface,简称JVM TI。”

“那怎么通过socket来使用啊?” GUI大臣急了。

IO大臣说:“封装一下嘛,程序员可以写个程序(Agent),充当通信的桥梁 。”

04 通信

GUI大臣说:“唉,这就麻烦了,我们还得考虑通信的协议问题!”

IO大臣:“那是,刚才你提的那一大堆调试的需求,都需要能通过网络发给JVM才行,不过不用担心,这方面我擅长,让我来制定一个协议,供调试器和JVM 通信 !这个协议的名称就叫 (JDWP)Java Debug Wire Protocol 吧。”

IO大臣看到JVM大臣的JVM TI,心中痒痒,也急不可耐地提出了创造了属于自己的缩写。

创造通信协议的机会可不多,IO大臣浮现出一幅调试器和JVM通信的场景:

双方先来一个“握手”,表明通信要开始了,然后调试器可以发送命令给JVM,JVM处理以后发送响应,还可以主动向调试器推送事件,嗯,这个协议应该是异步的......

05 调试器

GUI大臣看到这这张图,立刻意识到一个问题:“如果我们把JVM关于调试的能力使用JDWP这个协议的方式暴露出来,那调试器可以使用任意语言来编写啊!”

IO大臣笑道:“是啊,可不仅仅是你老兄的Swing, AWT,别人用C, C++, Python, C#都可以写一个调试器。”

GUI大臣说到:“不不,陛下看到这个设计肯定会发怒的,我们还是提供一个Java版本的接口吧,让这个接口把JDWP还有什么JVM TI都给封装起来,主要供我们的Java IDE来使用,来集成。”

看到JVM大臣提出了JVM TI ,IO大臣提出了JDWP,自己没有,怎么在陛下那里交差?GUI大臣赶紧说:“嗯,我希望这个接口叫做 JDI( Java Debug Interface),怎么样?”

三位大臣相视一笑,心照不宣, 这下平衡了。

06 早朝

又是早朝, JVM大臣代表三人向国王献上了设计图,着重强调了自己提出的JVM TI是多么精妙,完美,至于JDWP, JDI, JVM大臣语焉不详,一笔带过, 气得IO大臣, GUI大臣吹胡子瞪眼。

国王看着设计图,频频点头:“嗯,层次划分得不错,程序员可以直接使用JVM 提供的接口,也可以用JDWP, 还可以用JDI..... ”

三位大臣甚感佩服,国王就是厉害。

可是国王的脸色很快多云转阴:“只有设计图,代码呢?Talk is cheap , show me the code !”

就在JVM大臣懵逼之时, GUI大臣从怀中掏出一张写满代码的纸,双手呈给了国王,还回过头来对JVM大臣神秘一笑。

国王拿到了代码,只见上面写着:

创建一个断点:

ClassPrepareEvent event = ....略....
ClassType classType = (ClassType) event.referenceType();
// 获取表示第10行的Location对象
Location location = classType.locationsOfLine(10).get(0);
// 通过Location对象创建一个断点
BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location);
bpReq.enable();   

(可左右滑动)

在断点处获取变量的值:

// 到达了一个断点
BreakpointEvent event = .....略......
//获取当前的线程
ThreadReference threadReference = event.thread();
//获取当前的栈帧
StackFrame stackFrame = threadReference.frame(0);
//从栈帧中得到本地变量 i
LocalVariable localVariable = stackFrame.visibleVariableByName("i");
Value value = stackFrame.getValue(localVariable);
int i = ((IntegerValue) value).value();
System.out.println("The local variable " + "i" + " is " + i);

(可左右滑动)

国王扫了一眼,龙颜大悦,说到:“爱卿多虑了,还给我加了这么多注释,其实不加注释我也看得懂,你展示的就是通过JDI这个接口创建断点,然后在断点处获取变量的值。我知道这代码的背后其实会用JDWP协议向JVM TI发出请求,因为所有的数据都在那里,对不对?”

JVM大臣赶紧说:“陛下圣明,一下子就点透了我们几个小心思。”

“各位爱卿受累了,赏黄马褂,朕打算把你们三个人创建的东西合起来起一个名,叫Java Platform Debugger Architecture,JPDA, 怎么样?”

三人哪敢反对?如小鸡啄米般纷纷点头称颂,从此, JPDA就成为了Java帝国有关调试的标准,各个IDE逐渐都用来起来。

后记:实际上JDK最早只有 JVM DI (Debugger Interface) 和 JVM PI (Profile Interface),后来才出现JVM TI,并不是文章中所说的一步到位。

< END >

本文分享自微信公众号 - 养码场(yangmachang0)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一篇文章讲清楚Java基本数据类型,常量池,以及自动拆装箱的秘密

    Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

    Java技术江湖
  • 漫话:如何给女朋友解释什么是IO中的阻塞、非阻塞、同步、异步?

    周末在家加班,正在疯狂的撸代码,女朋友很开心的跑过来,手里拿着他刚刚画好的一副漫画。

    掌上编程
  • 聊聊nacos的HttpHealthCheckProcessor

    nacos-1.1.3/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthChec...

    codecraft
  • 大数据的“动物管理员” ZooKeeper

    Hadoop的框架里面经常有听到PIG(猪)、HIVE(小密蜂)、Hadoop(大象)......,就像是动物园的小动物,这些小动物的管理者就是...

    希望的田野
  • 冰蝎动态二进制加密WebShell特征分析

    冰蝎一款新型加密网站管理客户端,在实际的渗透测试过程中有非常不错的效果,能绕过目前市场上的大部分WAF、探针设备。本文将通过在虚拟环境中使用冰蝎,通过wires...

    FB客服
  • 在单台云主机搭伪分布式hadoop环境

    Hadoop是大数据的基础框架模型,处理大数据,不应只谈偏向业务环境的大数据(如超市买婴儿尿不湿同时还应该推荐啤酒的经典案例),作为解决方案经理,技术是不能缺少...

    希望的田野
  • 架构师负责订规范,你负责执行!

    心意相通的研发之间,本不需要BB这BB那搞些约束。但宁教我心徒枉然,不教银光惹尘埃。过分的放纵爱自由,那就是一去不复返了。

    xjjdog
  • JAVA多线程使用场景和注意事项简版

    我曾经对自己的小弟说,如果你实在搞不清楚什么时候用HashMap,什么时候用ConcurrentHashMap,那么就用后者,你的代码bug会很少。

    xjjdog
  • SpringBoot集成JPA

    在SpringBoot中,通过Spring Data JPA 和 Spring Data Rest可以快速构建出一个RESTFul应用。

    Noneplus
  • Hadoop+Hive+HBase+Spark 集群部署(一)

    本文由 bytebye 创作 本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

    ByteBye

扫码关注云+社区

领取腾讯云代金券