`
nijiaben
  • 浏览: 130491 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

从日志上理解java调试机制

    博客分类:
  • JVM
 
阅读更多

       java调试我想大家都玩过,或许因为非常完美地集成到了我们日常的IDE里,比如eclipse,netbeans,可是大家有没有想过它是怎么实现的呢,接下来我会从vm中打印的调试日志来跟大家认识下这个调试体系。

 

      大家都是一点IDE里面的调试按钮,就轻松建立了调试环境,IDE其实是帮你做了些你可能不知道的事,请看如下图


 你将会看到如下内容


其实这就是eclipse为你构建的命令行,从中筛选出如下命令行语句

-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:51823 

这就是关键了,熟悉agent的人应该都知道这是加载了一个叫做jdwp的agent,也就是说vm会去找一个叫做libjdwp.so/libjdwp.dylib的动态库,于是通过nm看看里面究竟有些什么方法



       赫然看到了Agent的几个方法,_Agent_OnLoad和_Agent_OnUnLoad,于是更加肯定这个动态库就是一个agent实例了。

有了它自然想看看这个Agent加载的时候干了些什么事。先不急于看里面的实现,一个好的模块或者应用,往往通过详细的日志就可以知道它的大致情况,其实jdwp也是有详细的日志可看的,在上面的-agentlib:jdwp后面再加上一个参数logflags=0xfff,即-agentlib:jdwp=transport=dt_socket,suspend=y,logflags=0xfff,然后再次执行java命令,你将会在你的当前目录下生成一个类似jdwp.log.297的文件,297其实是进程的ID,那当我们仅仅是启动,还没有做任何操作的时候jdwp都干了些什么?

 

[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=MISC:"debugInit.c":296;;PID=297;THR=t@60821504|:Onload: transport=dt_socket,address=8000,server=y,suspend=n,logflags=0xfff|#]
[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=JVMTI:"debugInit.c":300;;PID=297;THR=t@60821504|:GetPotentialCapabilities()|#]
[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=JVMTI:"debugInit.c":359;;PID=297;THR=t@60821504|:AddCapabilities()|#]
[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=JVMTI:"debugInit.c":128;;PID=297;THR=t@60821504|:SetEventNotificationMode()|#]
[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=JVMTI:"debugInit.c":128;;PID=297;THR=t@60821504|:SetEventNotificationMode()|#]
[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=JVMTI:"debugInit.c":390;;PID=297;THR=t@60821504|:SetEventCallbacks()|#]
[#|29.05.2013 21:20:18.161 CST|FINEST|J2SE1.5|jdwp|LOC=MISC:"debugInit.c":398;;PID=297;THR=t@60821504|:OnLoad: DONE|#]

       由于篇幅的问题,就只把部分日志列出来解释下,从上面我们可以获得的执行的时间,执行的环境类型,执行的哪个文件的哪一行,进程ID,线程地址,然后就是真正的日志信息等。

 

首先看看LOC,是location_type的简称,那么它包括多少种呢,我想从下面的宏定义上可以看出来

 

#define    JDWP_LOG_JVM         0x00000001
#define    JDWP_LOG_JNI         0x00000002
#define    JDWP_LOG_JVMTI       0x00000004
#define    JDWP_LOG_MISC        0x00000008
#define    JDWP_LOG_STEP        0x00000010
#define    JDWP_LOG_LOC         0x00000020
#define    JDWP_LOG_CB          0x00000040
#define    JDWP_LOG_ERROR       0x00000080
#define    JDWP_LOG_ALL         0xffffffff

 agent的实现离不开jvmti,jdwp也同样如此,那么它究竟想利用jvmti里的哪些能力呢,从下面的代码基本可以看出来了

 

 

  /* Fill in ones that we must have */
    (void)memset(&needed_capabilities,0,sizeof(needed_capabilities));
    needed_capabilities.can_access_local_variables              = 1;
    needed_capabilities.can_generate_single_step_events         = 1;
    needed_capabilities.can_generate_exception_events           = 1;
    needed_capabilities.can_generate_frame_pop_events           = 1;
    needed_capabilities.can_generate_breakpoint_events          = 1;
    needed_capabilities.can_suspend                             = 1;
    needed_capabilities.can_generate_method_entry_events        = 1;
    needed_capabilities.can_generate_method_exit_events         = 1;
    needed_capabilities.can_generate_garbage_collection_events  = 1;
    needed_capabilities.can_maintain_original_method_order      = 1;
    needed_capabilities.can_generate_monitor_events             = 1;
    needed_capabilities.can_tag_objects                         = 1;

    /* And what potential ones that would be nice to have */
    needed_capabilities.can_force_early_return
                = potential_capabilities.can_force_early_return;
    needed_capabilities.can_generate_field_modification_events
                = potential_capabilities.can_generate_field_modification_events;
    needed_capabilities.can_generate_field_access_events
                = potential_capabilities.can_generate_field_access_events;
    needed_capabilities.can_get_bytecodes
                = potential_capabilities.can_get_bytecodes;
    needed_capabilities.can_get_synthetic_attribute
                = potential_capabilities.can_get_synthetic_attribute;
    needed_capabilities.can_get_owned_monitor_info
                = potential_capabilities.can_get_owned_monitor_info;
    needed_capabilities.can_get_current_contended_monitor
                = potential_capabilities.can_get_current_contended_monitor;
    needed_capabilities.can_get_monitor_info
                = potential_capabilities.can_get_monitor_info;
    needed_capabilities.can_pop_frame
                = potential_capabilities.can_pop_frame;
    needed_capabilities.can_redefine_classes
                = potential_capabilities.can_redefine_classes;
    needed_capabilities.can_redefine_any_class
                = potential_capabilities.can_redefine_any_class;
    needed_capabilities.can_get_owned_monitor_stack_depth_info
        = potential_capabilities.can_get_owned_monitor_stack_depth_info;
    needed_capabilities.can_get_constant_pool
                = potential_capabilities.can_get_constant_pool;
    {
        needed_capabilities.can_get_source_debug_extension      = 1;
        needed_capabilities.can_get_source_file_name            = 1;
        needed_capabilities.can_get_line_numbers                = 1;
        needed_capabilities.can_signal_thread
                = potential_capabilities.can_signal_thread;
    }

         这就对为什么我们可以通过调试体系可以看到本地变量的值,可以一步一步push/pop frame等能力不足为奇了。

 

因为agent是在vm初始化过程中加载的,vm的环境其实很多还没准备好,所以jvmti提供了一种能力---事件通知,在某个特定事件发生的时候,agent可以通过注册一些回调事件来做一些特殊的工作,比如在vm初始化完成之后,在类文件加载之后,在类被加载之后等等都可以做一些相关的事情,jdwp主要注册了几个回调事件,重点是vm初始化的回调函数,此时才是真正的完成jdwp的初始化动作,这也是为什么我们的调试体系不能对所有的java代码进行跟踪的原因了,比如说AppClassLoader的创建过程,这个其实都是在vm初始化过程中创建的,但是这个时候jdwp还没有初始化呢,自然也无法完成我们的debug过程了。

 

    gdata->callbacks.VMInit             = &cbEarlyVMInit;
    gdata->callbacks.VMDeath            = &cbEarlyVMDeath;
    gdata->callbacks.Exception  = &cbEarlyException;

     在cbEarlyVMInit中干的事就不细说了,可以通过jdwp的日志大概知道干了些啥,但是有点还是要说下,假设要大家去设计实现这个功能,大家应该都会想到它的通信场景,类似命令模式,客户端不断发命令到服务端,服务端收到命令之后然后将请求结果反馈回来,比如说我们把鼠标放到某个变量上面,我们会看到弹出个小提示板,会把这个对象的值树状列出来,其实jdwp也就是这么实现的,那我们就可能想知道jdwp到底提供了哪些命令

 

 

    l1Array[JDWP_COMMAND_SET(VirtualMachine)] = (void *)VirtualMachine_Cmds;
    l1Array[JDWP_COMMAND_SET(ReferenceType)] = (void *)ReferenceType_Cmds;
    l1Array[JDWP_COMMAND_SET(ClassType)] = (void *)ClassType_Cmds;
    l1Array[JDWP_COMMAND_SET(ArrayType)] = (void *)ArrayType_Cmds;

    l1Array[JDWP_COMMAND_SET(Field)] = (void *)Field_Cmds;
    l1Array[JDWP_COMMAND_SET(Method)] = (void *)Method_Cmds;
    l1Array[JDWP_COMMAND_SET(ObjectReference)] = (void *)ObjectReference_Cmds;
    l1Array[JDWP_COMMAND_SET(StringReference)] = (void *)StringReference_Cmds;
    l1Array[JDWP_COMMAND_SET(ThreadReference)] = (void *)ThreadReference_Cmds;
    l1Array[JDWP_COMMAND_SET(ThreadGroupReference)] = (void *)ThreadGroupReference_Cmds;
    l1Array[JDWP_COMMAND_SET(ClassLoaderReference)] = (void *)ClassLoaderReference_Cmds;
    l1Array[JDWP_COMMAND_SET(ArrayReference)] = (void *)ArrayReference_Cmds;
    l1Array[JDWP_COMMAND_SET(EventRequest)] = (void *)EventRequest_Cmds;
    l1Array[JDWP_COMMAND_SET(StackFrame)] = (void *)StackFrame_Cmds;
    l1Array[JDWP_COMMAND_SET(ClassObjectReference)] = (void *)ClassObjectReference_Cmds;

 从上面我们基本看到列出的都是分类,比如我们想看字段的,执行方法的,甚至vm的,那每个分类势必包含一些关键的命令,比如

 

 

void *VirtualMachine_Cmds[] = { (void *)21
    ,(void *)version
    ,(void *)classesForSignature
    ,(void *)allClasses
    ,(void *)getAllThreads
    ,(void *)topLevelThreadGroups
    ,(void *)dispose
    ,(void *)idSizes
    ,(void *)suspend
    ,(void *)resume
    ,(void *)doExit
    ,(void *)createString
    ,(void *)capabilities
    ,(void *)classPaths
    ,(void *)disposeObjects
    ,(void *)holdEvents
    ,(void *)releaseEvents
    ,(void *)capabilitiesNew
    ,(void *)redefineClasses
    ,(void *)setDefaultStratum
    ,(void *)allClassesWithGeneric
    ,(void *)instanceCounts
};

 这就列出了我们想看vm相关信息或者操作的一些命令,其实这些都是一些function

 

 

[#|29.05.2013 21:21:22.439 CST|FINEST|J2SE1.5|jdwp|LOC=MISC:"debugLoop.c":153;;PID=297;THR=t@290471936|:Command set 1, command 10|#]

 

假如我们操作之后有上面这么一条日志,我们看到取的指令集合是1,想执行这个集合中的第10条命令,指令集合为1,说明取的是上面的llArray[1],也就是上面的VirtualMachine指令集合,10表示我们找的是 VirtualMachine中的doExit这条指令,也就是说客户端希望你服务端执行这条指令,

 

static jboolean
doExit(PacketInputStream *in, PacketOutputStream *out)
{
    jint exitCode;

    exitCode = inStream_readInt(in);
    if (gdata->vmDead) {
        /* quietly ignore */
        return JNI_FALSE;
    }

    /* We send the reply from here because we are about to exit. */
    if (inStream_error(in)) {
        outStream_setError(out, inStream_error(in));
    }
    outStream_sendReply(out);

    forceExit(exitCode);

    /* Shouldn't get here */
    JDI_ASSERT(JNI_FALSE);

    /* Shut up the compiler */
    return JNI_FALSE;

}
/* All process exit() calls come from here */
void
forceExit(int exit_code)
{
    /* make sure the transport is closed down before we exit() */
    transport_close();
    exit(exit_code);
}

      大家也猜到了,这其实就是退出vm,其实这也是我今天想写这篇文章的一个原因,我们这边线下环境一个系统,运行一段时间之后就突然间进程消失了,没有任何的日志,排除了人为的kill,System.exit,没有crash日志,更没有coredump,实在是诡异,跟了两天,突然在远程调试的时候发现了如下选项

 



 所以我不得不猜想是它导致的,经过自己测试,当我点击

这个的时候,进程果然退出了,没有任何迹象,所以晚上花了点时间写了这篇文章记录下这诡异的问题

这个暂时就介绍到这里,虽然还比较肤浅,有机会再从原理角度介绍jdwp的具体实现。

 

  • 大小: 393.2 KB
  • 大小: 281.8 KB
  • 大小: 91.9 KB
  • 大小: 38.6 KB
  • 大小: 9.8 KB
  • 大小: 393.2 KB
5
3
分享到:
评论
6 楼 dxm1986 2013-08-19  
假笨越来越深入了。。。。
5 楼 kely39 2013-05-31  
高深
4 楼 chainhou 2013-05-30  
楼主是在阿里支付宝啊?
选中“Allow termination of remote VM”应该和在debug时选中具体的debug的应用右击,选“terminate and remove”一样的
3 楼 xiangxm 2013-05-30  
牛人aaa
2 楼 dsjt 2013-05-30  
很高深,以前从来没注意过
1 楼 商人shang 2013-05-30  
学习了,可是感觉有点高深,看不懂啊

相关推荐

    JAVA Udp调试工具 Java版本

    Java UDP调试工具是一种用于测试和调试UDP(User Datagram Protocol)网络通信的应用程序。...通过深入理解Java的网络编程API和这个工具的内部机制,开发者可以更好地理解和优化他们的UDP通信代码。

    Java串口通讯及串口调试助手

    - **保存日志**:将通信记录保存为文本文件,便于后续分析和调试。 在实际项目中,Java串口通讯可能涉及多线程处理,以确保读取和发送数据的并发性。此外,由于串口通讯的实时性要求,还可能需要用到缓冲区管理和...

    Java日志控件

    Java日志控件是一种在Java EE环境中用于日志监控的组件,它专注于提供对应用程序日志的高效管理和控制。此控件设计的核心理念是通过切面编程(Aspect-Oriented Programming, AOP)来实现日志拦截,允许开发者在不...

    Java程序日志类jar包

    SLF4J提供了一种在不同日志框架之间切换的机制,比如从Log4j切换到logback,而无需修改代码。 3. **Logback**: 由Log4j的创始人Ceki Gülcü设计,作为Log4j的替代品,Logback被认为是比Log4j更高效、更灵活的日志...

    JAVA高端进阶开发课程 JAVA应用程序调试技术 从实战角度出发学习JAVA应用程序调试.rar

    在Java编程领域,调试是开发过程中至...无论你是经验丰富的开发者还是初学者,都能从中受益匪浅,提升自己的Java调试能力。通过实践这些技术,你将能够更加高效地调试和优化你的Java应用程序,实现更高质量的软件开发。

    java动态代理机制

    Java动态代理机制是Java语言提供的一种强大的功能,它允许在运行时创建代理对象来实现特定接口,从而可以灵活地扩展或增强已有代码的功能。在Java中,动态代理主要通过两个类来实现:`java.lang.reflect.Proxy` 和 `...

    java日志.txt

    Java日志框架是Java平台提供的一套标准日志记录机制,用于记录应用程序运行时的状态信息。它支持不同级别的日志记录,如`SEVERE`、`WARNING`、`INFO`等,并允许用户自定义日志记录级别、格式以及输出目标等配置。...

    Java日志框架整合详情

    Java日志框架是Java开发中不可或缺的一部分,它用于记录应用程序运行过程中的各种信息,帮助开发者在出现问题时进行调试和分析。日志框架的选择和整合对于一个项目来说至关重要,因为它不仅影响到开发效率,还直接...

    简单Java&XML数据库版网络日志

    首先,我们来理解Java反射机制。反射是Java提供的一种强大的工具,允许程序在运行时检查和操作类、接口、对象等的内部结构。通过反射,我们可以动态地创建对象、调用方法和访问字段,这在处理不确定类型或需要动态...

    资料-Java日志.zip

    本资料"Java日志.zip"包含的资源旨在帮助开发者理解和应用Java日志机制,其中可能涵盖了日志框架的介绍、示例代码以及相关的学习笔记。 1. **日志框架** Java平台上有多种日志框架,如Java内置的日志API(java....

    用Java事件处理机制实现录制回放功能

    总结来说,实现Java GUI应用的录制回放功能,需要理解Java事件处理机制的原理,包括事件监听器、适配器和事件模型的演变。通过捕获和回放用户的操作,可以有效地进行自动化测试和应用演示,提高开发和调试效率。

    利用Spring机制记录系统日志

    在IT行业中,日志记录是系统监控和故障排查的关键环节。Spring框架,作为一个广泛应用的Java企业级开发框架,提供了丰富...正确实施这一机制,能够有效地提升系统的可维护性和稳定性,帮助开发者更好地理解和调试系统。

    异步写日志

    综上所述,异步写日志是一种提升应用程序性能的技术,它通过将日志写入操作从主线程中解耦,避免了同步日志可能导致的阻塞问题。在Java环境中,我们可以利用各种日志框架提供的功能,结合Java的并发工具,实现高效且...

    java-操作记录-对比操作前后不同

    通过阅读和分析这些class的源码,我们可以更深入地理解如何在实际项目中集成和使用操作记录机制。这些类可能会包含方法来初始化日志框架,设置日志级别,以及包装操作的开始和结束日志消息。 对比操作前后不同,...

    Java 日志管理+log4j

    Java日志管理是Java开发中不可或缺的一部分,它用于记录应用程序运行过程中的各种信息,包括错误、警告、调试信息等,帮助开发者追踪问题、优化性能以及理解程序运行状态。Log4j是Java领域广泛使用的日志框架之一,...

    JAVA打印机插件java打印失败打印机工具

    在Java编程环境中,打印机插件是用于实现程序与物理打印机之间的通信的重要组件。...通过深入理解Java的打印API,仔细排查系统设置,以及适时更新Java和打印机驱动,通常可以解决大多数打印失败的问题。

    02-5的深入理解Java虚拟机书的学习-上篇

    《深入理解Java虚拟机》是一本深度探讨Java技术体系中Java虚拟机(JVM)的权威书籍,马士兵的JVM调优参考资料则为实践应用提供了丰富的指导。本压缩包包含了一系列与Java虚拟机相关的文档,涵盖了从基础到进阶的各种...

    Java_debug_Document.rar_Java_debug_Document

    3. **异常处理**:理解Java异常的机制,包括检查异常和运行时异常,以及如何通过try-catch-finally语句块来捕获和处理异常,这对于定位错误原因至关重要。 4. **日志记录**:学会使用如Log4j、SLF4J、Java内置的...

    eoms.rar_eo_eoms_java 日志_java 权限_权限管理

    Java提供的如Log4j、SLF4J或Java.util.logging等日志框架,能够帮助开发者对应用程序运行过程中的信息进行详细记录,包括错误、警告、调试信息等,便于后期分析系统性能、定位问题以及优化代码。同时,日志还可以...

    api接口说明调试

    本示例主要关注如何使用Java进行API接口的管理和调试,以支持App开发人员更好地理解和测试API。下面将详细阐述相关知识点。 一、API接口设计 API接口设计是开发过程中的关键步骤,它定义了服务提供者与服务消费者...

Global site tag (gtag.js) - Google Analytics