夜已深,但是Java第一代国王却无心睡眠,帝国刚刚建立,东边的C/C++王国虎视眈眈,随时准备把新生的王国扼杀在摇篮中。
今日GUI大臣上奏,说帝国子民抱怨运行速度慢,这一点Java国王也没有好办法,解释执行嘛,肯定比不上编译好的程序,不过Java国王已经下令去研发HotSpot了,等到儿子即位就会大有改观。
不过GUI大臣提出的另外一个问题的确让人发愁:帝国子民写出的程序调试困难, 大家得用最原始的System.out.println()来查看变量,定位错误。
这怎么能行?程序不能调试,相当于瘸了一条腿啊!这将严重影响新生Java帝国的找Bug事业。
第二天早朝,眼圈发黑的国王把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大臣,你们三个通力合作,把这一套规范给制定出来!”
三位大臣不敢怠慢,一退朝就急忙赶到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),充当通信的桥梁 。”
GUI大臣说:“唉,这就麻烦了,我们还得考虑通信的协议问题!”
IO大臣:“那是,刚才你提的那一大堆调试的需求,都需要能通过网络发给JVM才行,不过不用担心,这方面我擅长,让我来制定一个协议,供调试器和JVM 通信 !这个协议的名称就叫 (JDWP)Java Debug Wire Protocol 吧。”
IO大臣看到JVM大臣的JVM TI,心中痒痒,也急不可耐地提出了创造了属于自己的缩写。
创造通信协议的机会可不多,IO大臣浮现出一幅调试器和JVM通信的场景:
双方先来一个“握手”,表明通信要开始了,然后调试器可以发送命令给JVM,JVM处理以后发送响应,还可以主动向调试器推送事件,嗯,这个协议应该是异步的......
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),怎么样?”
三位大臣相视一笑,心照不宣, 这下平衡了。
又是早朝, 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,并不是文章中所说的一步到位。
转载:https://blog.csdn.net/coderising/article/details/100985615