呵呵 最近有一些 需要远程调试 flink 代码的需求
然后 太久了不用, 有些 生疏, 然后 碰到了一些问题
如下配置添加到了 flink taskManager 上面之后, 发现 taskManager 一直没有启动起来
java -Xrunjdwp:transport=dt_socket,suspend=y,server=y,address=3317 com.hx.test12.Test13RemoteDebug
然后 使用 jps 查看进程, taskmanage 对应的进程一直查询不出来, 最终得到的显示是 "-- main class information unavailable"
然后 才在网上搜索了一下 这个 suspend=y 的意思
呵呵 当然网上搜索到的那是一个 理解, 但是缺少一些 实质性的一些判断 来让你确信这个理解的东西
另外 我们还可以看一下 jps 在这里 为什么显示的是 "-- main class information unavailable", 另外就是 jps 明显停顿了几秒, 为什么会停顿几秒 ?
以下调试 vm 部分基于 jdk9, 其他基于 jdk8
- /**
- * Test13RemoteDebug
- *
- * @author Jerry.X.He <970655147@qq.com>
- * @version 1.0
- * @date 2021-11-01 18:53
- */
- public class Test13RemoteDebug {
-
- // Test13RemoteDebug
- // java -Xrunjdwp:transport=dt_socket,suspend=y,server=y,address=3317 com.hx.test12.Test13RemoteDebug
- // 新建远程连接 配置 ip, port 进行远程调试
- public static void main(String[] args) throws Exception {
-
- int i = 0;
- while (true) {
- System.out.println(i++);
- Thread.sleep(3000);
- }
-
- }
-
- }
jps 信息如下
- master:jdk jerry$ jps
- 3266 -- main class information unavailable
jstack 查看 main 的堆栈信息如下
看不到任何堆栈信息, 因为阻塞的时候还没有开始执行任何 java 代码
- "main" #1 prio=5 os_prio=31 tid=0x00007fd877003800 nid=0x1a03 runnable [0x0000000000000000]
- java.lang.Thread.State: RUNNABLE
- JavaThread state: _thread_blocked
- Thread: 0x00007fd877003800 [0x1a03] State: _at_safepoint _has_called_back 0 _at_poll_safepoint 0
- JavaThread state: _thread_blocked
vm 是处于 createVM 函数中, 因为我们配置了 suspend=y, 因此 这里需要等待 debugMonitor 被唤醒[gdata->jvmti]
从整理 createVM 的流程上来看, 已经创建了 vm, 已经初始化过了
注意这张图, 和待会儿 jps 的 "-- main class information unavailable" 有关系
新建一个 java 远程连接, 并连接
正常流程的唤醒是在 有 debugger 连接上了之后, 这里调用的是 debugMonitorNotifyAll, 唤醒的就是上面 wait 的 main 线程
这里看到的上面 (*t) -> Accept 里面除了 正常的 tcp 握手之外, 包含的是一个 JDWP 逻辑意义上的一个 握手, 客户端需要发送 "JDWP-Handshake" 这十四个字节序列到 服务端
是由 JDWP 规范约束的
main 线程被唤醒之后, 继续走 createVM 之后的流程, 程序 正常启动
程序正常执行
获取对应的进程的启动命令的时候, 需要 attach 到给定的 vm, 获取 PerfDataPrologue 的信息, 判断 vm 是否准备好了, 这里等待 了 syncWaitMs[默认是5s], 可是一直没有等到 PerfDataPrologue.accessable, 最终抛出了异常 “Could not synchronize with target”
异常来到 jps 外层, 我们看到的 “-- main class information unavailable” 是获取 mainClass 阶段的一个默认的错误信息
意思是只要是 获取 mainClass 阶段发生了任意异常, 我们都会得到 "-- main class information unavailable"
我们来看一下 PerfDataPrologue.access 是哪里被设置为 true 的?
可以看到的是在 createVM 里面, 然后你可以回顾一下 上面的 wait 的哪一张图片, create_vm_timer.end() 是 createVM 里面的倒数第几行代码
读取的是 hsperfdata_* 文件夹下面的信息, 里面的是各个进程号, 然后根据 进程号获取 jps 本身需要的相关信息
- master:jdk jerry$ ll /var/folders/pw/lb8dvl7d6474r5plrnwtcp180000gn/T/hsperfdata_jerry/
- total 576
- -rw------- 1 jerry staff 32768 Nov 7 16:01 1770
- -rw------- 1 jerry staff 32768 Nov 7 19:55 3338
- -rw------- 1 jerry staff 32768 Nov 7 20:18 3520
- -rw------- 1 jerry staff 32768 Nov 7 20:20 3526
- -rw------- 1 jerry staff 32768 Nov 7 20:20 3527
- -rw------- 1 jerry staff 32768 Nov 7 20:24 3639
- -rw------- 1 jerry staff 32768 Nov 7 10:51 661
- -rw------- 1 jerry staff 32768 Nov 7 10:53 760
- -rw------- 1 jerry staff 32768 Nov 7 11:29 948
呵呵 演示版本, 以后有机会 放出来
- import com.alibaba.fastjson.JSON;
- import com.hx.codec.utils.IoUtils;
- import com.hx.net.client.ClientChannelHandler;
- import com.hx.net.client.jdwp.JdwpClient;
- import com.hx.net.common.BaseProtocolTests;
- import com.hx.net.config.ClientConfig;
- import com.hx.net.protocol.jdwp.JdwpClientProtocol;
- import com.hx.net.protocol.jdwp.common.JdwpMessage;
- import com.hx.net.protocol.jdwp.msg.JdwpHandShake;
- import com.hx.net.protocol.jdwp.msg.JdwpRequest;
- import com.hx.net.protocol.jdwp.msg.JdwpResponse;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelHandler;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.SimpleChannelInboundHandler;
- import org.junit.FixMethodOrder;
- import org.junit.Test;
- import org.junit.runners.MethodSorters;
-
- /**
- * Test08JdwpClientProtocol
- *
- * @author Jerry.X.He
- * @version 1.0
- * @date 2021-11-07 16:30
- */
- @FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
- public class Test08JdwpClientProtocol extends BaseProtocolTests {
-
- @Test
- public void test02Server() throws Exception {
-
- JdwpClientProtocol clientProtocol = new JdwpClientProtocol();
-
- ClientConfig clientConfig = new ClientConfig();
- clientConfig.setHost("localhost");
- clientConfig.setPort(3317);
- JdwpClient client = new JdwpClient(clientConfig, clientProtocol, new JdwpClientHandler(), new JdwpSocketChannelHandler());
-
- client.start();
-
- // sleep for biz, then stop
- IoUtils.sleep(15000 * 1000);
-
- client.stop();
-
- }
-
- // ------------------------------------------ assist methods ------------------------------------------
-
- /**
- * JdwpClientHandler
- *
- * @author Jerry.X.He
- * @version 1.0
- * @date 2021-11-07 16:32
- */
- @ChannelHandler.Sharable
- static class JdwpClientHandler extends SimpleChannelInboundHandler<JdwpMessage> {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, JdwpMessage msg) throws Exception {
- LOGGER.info(" client received : {} ", JSON.toJSONString(msg));
- if (msg instanceof JdwpHandShake) {
- JdwpRequest entity = new JdwpRequest();
- // entity.setLen(11);
- entity.setId(1171);
- entity.setFlags(0);
- entity.setCmdSet(1);
- entity.setCmd(7);
- entity.setData(new Integer[]{});
- ctx.writeAndFlush(entity);
- } else if (msg instanceof JdwpResponse) {
-
- }
- }
- }
-
- /**
- * JdwpSocketChannelHandler
- *
- * @author Jerry.X.He
- * @version 1.0
- * @date 2021-11-07 16:33
- */
- static class JdwpSocketChannelHandler implements ClientChannelHandler {
- @Override
- public void doBiz(Channel channel) throws Exception {
- JdwpHandShake handShakeEntity = new JdwpHandShake();
- channel.writeAndFlush(handShakeEntity);
- System.in.read();
- }
- }
-
- }
日式输出如下
可以看到的是 客户端发送了 JDWP握手 信息之后, HotspotVM 回复了 JDWP握手回复 信息
客户端拿到 HotspotVM 的 JDWP握手回复 之后, 发送了一个 cmdSet 为 1, cmd 为 7 的一个请求过去, 此请求对应的 handler 是 VirtualMachine_Cmds.idSizes
然后 HotspotVM 响应给了我的客户端 5 个 8, 也就是对应的 idsSize 里面输出的 5 个 8
- [20:43:00.458] INFO com.hx.net.interceptor.common.ChannelStateInterceptor 23 channelActive - channelActive : [id: 0x27ca94e3, L:/127.0.0.1:54489 - R:localhost/127.0.0.1:3317]
- [20:43:00.662] INFO com.hx.net.protocol.Test08JdwpClientProtocol$JdwpClientHandler 151 channelRead0 - client received : {"echo":"JDWP-Handshake"}
- [20:43:00.732] INFO com.hx.net.protocol.Test08JdwpClientProtocol$JdwpClientHandler 151 channelRead0 - client received : {"data":[0,0,0,8,0,0,0,8,0,0,0,8,0,0,0,8,0,0,0,8],"errorCode":0,"flags":-128,"id":1171,"len":31}
- Disconnected from the target VM, address: '127.0.0.1:54486', transport: 'socket'
- [20:43:14.224] INFO com.hx.net.interceptor.common.ChannelStateInterceptor 29 channelInactive - channelInactive : [id: 0x27ca94e3, L:/127.0.0.1:54489 ! R:localhost/127.0.0.1:3317]
VirtualMachine_Cmds.idSizes 如下
在我们通常调试的场景中
HotspotVM 提供了 jvmti 的实现, jwdp 服务端的实现, 实现的是 调试相关操作 落地到 vm 的相关处理
idea 提供了 jdwp 客户端的实现, 和 jdi的调用 最终就是我们看到的这个前端这一套
奉上 jpda, jdwp 相关规范文档
Java™ Platform Debugger Architecture (JPDA)
提供一个完整的 usage, 让你不再迷茫
- ERROR: JDWP option syntax error: -agentlib:jdwp=trhelp
- master:classes jerry$ java -Xrunjdwp:help com.hx.test12.Test13RemoteDebug
- Java Debugger JDWP Agent Library
- --------------------------------
-
- (see http://java.sun.com/products/jpda for more information)
-
- jdwp usage: java -agentlib:jdwp=[help]|[<option>=<value>, ...]
-
- Option Name and Value Description Default
- --------------------- ----------- -------
- suspend=y|n wait on startup? y
- transport=<name> transport spec none
- address=<listen/attach address> transport spec ""
- server=y|n listen for debugger? n
- launch=<command line> run debugger on event none
- onthrow=<exception name> debug on throw none
- onuncaught=y|n debug on any uncaught? n
- timeout=<timeout value> for listen/attach in milliseconds n
- mutf8=y|n output modified utf-8 n
- quiet=y|n control over terminal messages n
-
- Obsolete Options
- ----------------
- strict=y|n
- stdalloc=y|n
-
- Examples
- --------
- - Using sockets connect to a debugger at a specific address:
- java -agentlib:jdwp=transport=dt_socket,address=localhost:8000 ...
- - Using sockets listen for a debugger to attach:
- java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y ...
-
- Notes
- -----
- - A timeout value of 0 (the default) is no timeout.
-
- Warnings
- --------
- - The older -Xrunjdwp interface can still be used, but will be removed in
- a future release, for example:
- java -Xdebug -Xrunjdwp:[help]|[<option>=<value>, ...]
完