青岛青岛思途教育

400-882-1633

全国学习专线 8:00-22:00
青岛青岛思途教育
青岛中享思途致力于高新技术人才的免费实训。  是一家集软件开发和IT教育实训为一体的科技实训学校。  力争为更多的全国学员提供真正具备口碑的技术实训和就业服务  
您当前的位置: >青岛汇学通 >青岛培训学校 >在Java中运用修复Bug的小技巧

青岛Java学校新闻

在Java中运用修复Bug的小技巧

发布时间:2018-12-26 10:49:23 已帮助:744人 来源:青岛青岛思途教育

在Java中运用修复Bug的小技巧

在Java中运用修复Bug的小技巧
文章介绍:

  Java是一种编程语言,相信很多熟知Java的同学都会碰到一种常见的问题那就是在Java中运用动态挂载的过程中经常会出现很多bug,那么针对性这些我们常见的bug我们应该如何解决呢?以下是小编整理的遇到bug时候要怎么解决的资料,一起来看看吧!

  大多数JVM具备Java的HotSwap特性,大部分开发者认为它仅仅是一个调试工具。利用这一特性,有可能在不重启Java进程条件下,改变Java方法的实现。典型的例子是使用IDE来编码。然而HotSwap可以在生产环境中实现这一功能。通过这种方式,不用停止运行程序,就可以扩展在线的应用程序,或者在运行的项目上修复小的错误。这篇文章中,我将演示动态绑定、应用运行期代码变化进行绑定、介绍一些工具API以及ByteBuddy库,这个库提供了一些API代码改变更方便。


  假设有一个正在运行的应用程序,通过校验HTTP请求中的X-Priority头部,来执行服务器的特殊处理。该校验使用下面的工具类来实现:

  classHeaderUtility{staticbooleanisPriorityCall(HttpServletRequestrequest){returnrequest.getHeader("X-Pirority")!=null;}}

  你发现错误了吗?这样的错误很常见,尤其是在测试代码中常量值分解为静态字段重用。在不太理想的情况下,这个错误只会在产品被安装的时候才被发现,其中头通过另外一个应用生成并没有拼写错误。

  修复这样的错误并不难。在持续交付的时代,重新部署一个新的版本只需要点击一下按钮。但在其他情况下,变更可能就不是那么简单了,重新部署过程可能比较复杂,其中停机是不允许的,带着错误运行可能会比较好。但HotSwap给我们提供了另外一种选择:在不重启应用的前提下进行小幅改动。

1
AttachAPI:使用动态附件来渗透另外一个JVM:

  为了修改一个运行中的Java程序,我们首先需要一种可以同处在运行状态的JVM进行通信的方式。因为Java的虚拟机实现是一个受到管理的系统,因此拥有进行这些操作的标准API。提问中涉及到的API被称作attachmentAPI,它是官方Java工具的一部分。使用这个由运行之中的JVM所暴露的API,能让第二个Java进程来同其进行通信。

  事实上,我们已经用到了该API:它已经由诸如VisualVM或者JavaMissionControl这样的调试和模拟工具进行了应用。应用这些附件的API并没有同日常使用的标准JavaAPI打包在一起,而是被打包到了一个特殊的文件之中,叫做tools.jar,它只包含了一个虚拟机的JDK打包发布版本。更糟糕的是,这个JAR文件的位置并没有进行设置,它在Windows、Linux,特别是在Macintosh上的VM都存在差别,不光文件的位置,连文件名也各异,有些发行版上就被叫做classes.jar。最后,IBM甚至决定对这个JAR中包含的一些类的名称进行修改,将所有com.sun类挪到com.ibm命名空间之中,又添了一个乱子。在Java9中,乱糟糟的状态才最终得以清理,tools.jar被Jigsaw的模块jdk.attach所替代。
  在对API的JAR(或者模块)进行了定位之后,我们就该让其对附件进程可用。在OpenJDK上,被用来连接到另外一个JVM的类叫做VirtualMachine,它向任何由位于同一台物理机器上的JDK或者是一个普通的HtpSpotJVM所运行的VM提供了一个入口点。在通过进程id附加到另外一台虚拟机上之后,我们就能够在目标VM指定的一个线程中运行一个JAR文件:

  //thefollowingstringsmustbeprovidedbyusStringprocessId=processId();StringjarFileName=jarFileName();VirtualMachinevirtualMachine=VirtualMachine.attach(processId);try{virtualMachine.loadAgent(jarFileName,"World!");}finally{virtualMachine.detach();}

  在收到一个JAR文件之后,目标虚拟机会查看该JAR的程序清单描述文件(manifest),并定位处在Premain-Class属性之下的类。这非常类似于VM执行一个主方法的方式。有了一个Java代理,VM和指定的进程id就可以查找到一个名为agentmain的方法,该方法可以由指定线程中的远程进程来执行:

  publicclassHelloWorldAgent{publicstaticvoidagentmain(Stringarg){System.out.println("Hello,"+arg);}}

  使用该API,只要我们知道一个JVM的进程id,就可以来在其上运行代码,打印出一条Hello,World!消息。甚至有可能同并不熟JDK发行版一部分的JVM进行通信,只要附加的VM是一个用来访问tools.jar的JDK安装程序。

2
InstrumentationAPI:修改目标VM的程序:

  到目前来看一切顺利。但是除了成功地同目标VM建立起了通信之外,我们还不能够修改目标VM上的代码以及BUG。后续的修改,Java代理可以定义第二参数来接收一个Instrumentation的实例。稍后要实现的接口提供了向几个底层方法的访问途径,它们中的一个就能够对已经加载的代码进行修改。

  为了修正“X-Pirority”错字,我们首先来假设为HeaderUtility引入了一个修复类,叫做typo.fix,就在我们下面所开发的BugFixAgent后面的代理的JAR文件中。此外,我们需要给予代理通过向manifest文件添加Can-Redefine-Classes:true来替换现有类的能力。有了现在这些东西,我们就可以使用instrumentation的API来对类进行重新定义,该API会接受一对已经加载的类以及用来执行类重定义的字节数组:

  publicclassBugFixAgent{publicstaticvoidagentmain(Stringarg,Instrumentationinst)throwsException{//onlyifheaderutilityisontheclasspath;otherwise,//aclasscanbefoundwithinanyclassloaderbyiterating//overthereturnvalueofInstrumentation::getAllLoadedClassesClass<?>headerUtility=Class.forName("HeaderUtility");//copythecontentsoftypo.fixintoabytearrayByteArrayOutputStreamoutput=newByteArrayOutputStream();try(InputStreaminput=BugFixAgent.class.getResourceAsStream("/typo.fix")){byte[]buffer=newbyte[1024];intlength;while((length=input.read(buffer))!=-1){output.write(buffer,0,length);}}//Applytheredefinitioninstrumentation.redefineClasses(newClassDefinition(headerUtility,output.toByteArray()));}}

  运行上述代码后,HeaderUtility类会被重定义以对应其修补的版本。对isPrivileged的任何后续调用现在将读取正确的头信息。作为一个小的附加说明,JVM可能会在应用类重定义时执行完全的垃圾回收,并且会对受影响的代码进行重新优化。总之,这会导致应用程序性能的短时下降。然而,在大多数情况下,这是较之完全重启进程更好的方式。

  当应用代码更改时,要确保新类定义了与它替换的类完全相同的字段、方法和修饰符。尝试修改任何此类属性的类重定义行为都会导致UnsupportedOperationException。现在HotSpot团队正试图去掉这个限制。此外,基于OpenJDK的动态代码演变虚拟机支持预览此功能。

3
使用ByteBuddy来追踪内存泄漏:

  一个如上述示例的简单的BUG修复代理在你熟悉了instrumentation的API的时候是比较容易实现的。只要更加深入一点,也可以在运行代理的时候,无需手动创建附加的class文件,而是通过重写现有的class来应用更多通用的代码修改。

4
字节码操作:

  编译好的Java代码所呈现的是一系列字节码指令。从这个角度来看,一个Java方法无非就是一个字节数组,其每一个字节都是在表示一个向运行时发出的指令,或者是最近一个指令的参数。每个字节对应其意义的映射在《Java虚拟机规范》中进行了定义,例如字节0xB1就是在指示VM从一个带有void返回类型的方法返回。因此,对字节码进行增强就是对一个方法的字节数字进行扩展,将我们想要应用的表示额外的业务逻辑指令包含进去。

  当然,逐个字节的操作会特别麻烦,而且容易出错。为了避免手工的处理,许多的库都提供了更高级一点的API,使用它们不需要我们直接同Java字节码打交道。这样的库其中就有一个叫做ByteBuddy(当然我就是该库的作者)。它的功能之一就是能够定义可以在方法原来的代码之前和之后被执行的模板方法。

  以上就是小编给大家整理的Java在中运用动态挂载实现Bug的热修复,想要了解更多关于Java的小技巧,快来青岛中享思途咨询【青岛Java+大数据工程课程


上一篇:如何零基础学习UI设计
下一篇:读完Java秘籍就是大神
关于我们 | 联系我们 | 青岛青岛思途教育地址:青岛校区总部/市南校区/ 咨询电话:400-882-1633
沪ICP备12032008号-7 网站地图 注册 登录 招生合作 版权/投诉 免责声明 更新时间:2024-04-23