100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 【华为云技术分享】程序员实用JDK小工具归纳

【华为云技术分享】程序员实用JDK小工具归纳

时间:2018-11-06 22:18:21

相关推荐

【华为云技术分享】程序员实用JDK小工具归纳

在JDK的安用装目录bin下,有一些有非常实用的小工具,可用于分析JVM初始配置、内存溢出异常等问题,我们接下来将对些常用的工具进行一些说明。

1. JDK小工具简介

在JDK的bin目录下面有一些小工具,如javac,jar,jstack,jstat等,在日常编译运行过程中有着不少的“额外”功能,那么它们是怎么工作的呢?

虽然这些文件本身已经被编译成可执行二进制文件了,但是其实它们的功能都是由tools.jar这个工具包(配合一些dll或者so本地库)完成的,每个可执行文件都对应一个包含main函数入口的java类(有兴趣可以阅读openJDK相关的源码

它们的对应关系如下(更多可去openJDK查阅):

javac com.sun.tools.javac.Mainjar sun.tools.jar.Mainjps sun.tools.jps.Jpsjstat sun.tools.jstat.Jstatjstack sun.tools.jstack.JStack...

2. tools.jar的使用

我们一般开发机器上都会安装JDK+jre,这时候,要用这些工具,直接运行二进制可执行文件就行了,但是有时候,机器上只有jre而没有JDK,我们就无法用了么?

如果你知道如上的对应关系的话,我们就可以"构造"出这些工具来(当然也可以把JDK安装一遍,本篇只是介绍另一种选择)

比如我们编写

//Hello.javapublic class Hello{public static void main(String[] args)throws Exception{while(true){test1();Thread.sleep(1000L);}}public static void test1(){test2();}public static void test2(){System.out.println("invoke test2");}}

可以验证如下功能转换关系

1.编译源文件:

javac Hello.java => java -cp tools.jar com.sun.tools.javac.Main Hello.java

结果一样,都可以生成Hello.class文件

然后我们开始运行java -cp . Hello

2.查看java进程:

jps => java -cp tools.jar sun.tools.jps.Jps

结果一样,如下:

4615 Jps11048 jar3003 Hello

3.动态查看内存:

jstat-gcutil30031003=>java-cptools.jarsun.tools.jstat.Jstat-gcutil30031003

发现结果是一样的

S0S1EOMCCSYGCYGCTFGCFGCTGCT0.000.004.000.0017.4219.6500.00000.0000.0000.000.004.000.0017.4219.6500.00000.0000.0000.000.004.000.0017.4219.6500.00000.0000.000

4.查看当前运行栈信息

正常情况,执行如下命令结果也是一样,可以正常输出

jstack3003=》java-cptools.jarsun.tools.jstack.JStack3003

但是有的jre安装不正常的时候,会报如下错误

Exceptioninthread"main"java.lang.UnsatisfiedLinkError:noattachinjava.library.path

这是因为jstack的运行需要attach本地库的支持,我们需要在系统变量里面配置上其路径,假如路径为/home/JDK/jre/bin/libattach.so

命令转换成

jstack3003=》java-Djava.library.path=/home/JDK/jre/bin-cptools.jarsun.tools.jstack.JStack3003

就可以实现了

在linux系统中是libattach.so,而在windows系统中是attach.dll,它提供了一个与本机jvm通信的能力,利用它可以与本地的jvm进行通信,许多java小工具就可能通过它来获取jvm运行时状态,也可以对jvm执行一些操作

3. attach使用

3.1编写agent.jar代理包

1. 编写一个Agent类

//Agent.javapublicclassAgent{publicstaticvoidagentmain(Stringargs,java.lang.instrument.Instrumentationinst){System.out.println("agent:"+args);}}

2. 编译Agent

java-cptools.jarcom.sun.tools.javac.MainAgent.java//或者javacAgent.java

3. 再编manifest.mf文件

//manifest.mfManifest-Version:1.0Agent-Class:AgentCan-Redefine-Classes:trueCan-Retransform-Classes:true

4. 把Agent.class和manifest.mf进行打包成agent.jar

java-cptools.jarsun.tools.jar.Main-cmfmanifest.mfagent.jarAgent.class//或者jar-cmfmanifest.mfagent.jarAgent.class

3.2 attach进程

1. 编写如下attach类,编译并执行

//AttachMain.javapublicclassAttachMain{publicstaticvoidmain(String[]args)throwsException{com.sun.tools.attach.VirtualMachinevm=com.sun.tools.attach.VirtualMachine.attach(args[0]);vm.loadAgent("agent.jar","injectparams");vm.detach();}}

2. 编译:

java-cptools.jarcom.sun.tools.javac.Main-cptools.jarAttachMain.java//或者javac-cptools.jarAttachMain.java

3. 执行attach

java-cp.:tools.jarAttachMain3003

4. 查看Hello进程有如下输出:

invoketest2invoketest2invoketest2invoketest2invoketest2invoketest2invoketest2agent:injectparamsinvoketest2

说明attach成功了,而且在目标java进程中引入了agent.jar这个包,并且在其中一个线程中执行了manifest文件中agentmain类的agentmain方法,详细原理可以见JVMTI的介绍,例如oracle的介绍

3.3用attach制作小工具

1. 写一个使进程OutOfMemory/StackOverFlow的工具

有了attach的方便使用,我们可以在agentmain中新起动一个线程(为避免把attach线程污染掉),在里面无限分配内存但不回收,就可以产生OOM或者stackoverflow

代码如下:

//Agent.javaforOOMpublicclassAgent{publicstaticvoidagentmain(Stringargs,java.lang.instrument.Instrumentationinst){newThread(){@Overridepublicvoidrun(){java.util.List<byte[]>list=newjava.util.ArrayList<byte[]>();try{while(true){list.add(newbyte[100*1024*1024]);Thread.sleep(100L);}}catch(InterruptedExceptione){}}}.start();}}//Agent.javaforstackoverflowpublicclassAgent{publicstaticvoidagentmain(Stringargs,java.lang.instrument.Instrumentationinst){newThread(){@Overridepublicvoidrun(){stackOver();}privatevoidstackOver(){stackOver();}}.start();}}

当测试OOM的时候,hello进程的输出为:

invoketest2Exceptioninthread"Thread-0"java.lang.OutOfMemoryError:JavaheapspaceatAgent$1.run(Agent.java:9)invoketest2invoketest2invoketest2

说明发生OOM了, 但是OOM线程退出了,其它线程还在正常运行。

如果我们需要进程在OOM的时候产生一些动作,我们可以在进程启动的时候增加一些OOM相关的VM参数

OOM的时候直接kill掉进程:-XX:OnOutOfMemoryError="kill -9 %p"

结果如下:

invoketest2invoketest2##java.lang.OutOfMemoryError:Javaheapspace#-XX:OnOutOfMemoryError="kill-9%p"#Executing/bin/sh-c"kill-926829"...Killed

OOM的时候直接退出进程:-XX:+ExitOnOutOfMemoryError

结果如下:

invoketest2invoketest2Terminatingduetojava.lang.OutOfMemoryError:Javaheapspace

OOM的时候进程crash掉:-XX:+CrashOnOutOfMemoryError

结果如下:

invoketest2invoketest2Abortingduetojava.lang.OutOfMemoryError:Javaheapspaceinvoketest2##AfatalerrorhasbeendetectedbytheJavaRuntimeEnvironment:##InternalError(debug.cpp:308),pid=42675,tid=0x00007f3710bf4700#fatalerror:OutOfMemoryencountered:Javaheapspace##JREversion:Java(TM)SERuntimeEnvironment(8.0_171-b11)(build1.8.0_171-b11)#JavaVM:JavaHotSpot(TM)64-BitServerVM(25.171-b11mixedmodelinux-amd64compressedoops)#Failedtowritecoredump.Coredumpshavebeendisabled.Toenablecoredumping,try"ulimit-cunlimited"beforestartingJavaagain##Anerrorreportfilewithmoreinformationissavedas:#/root/hanlang/test/hs_err_pid42675.log##Ifyouwouldliketosubmitabugreport,pleasevisit:#/bugreport/crash.jsp#Aborted

OOM的时候dump内存:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof

结果生成dump文件

4. asm的应用

4.1 asm使用原理

asm是一个java字节码工具,提供一种方便的函数/属性级别修改已经编译好的.class文件的方法, asm的简单使用原理介绍如下:

通过ClassReader读取.class文件的字节码内容,并生成语法树;ClassReader的方法accept(ClassVisitor classVisitor, int parsingOptions)功能是让classVisitor遍历语法树,默认ClassVisitor是一个代理类,需要有一个具体的实现在遍历语法树的时候做一些处理;用ClassWriter是ClassVisitor的一个实现,它的功能是把语法树转换成字节码;通常我们会定义一个自己的ClassVisitor,可以重写里面的一些方法来改写类处理逻辑,然后让ClassWriter把处理之后的语法树转换成字节码;

下面是具体的实现步骤:

1. 引入asm依赖包

<dependency><groupId>org.ow2.asm</groupId><artifactId>asm</artifactId><version>7.0</version></dependency><dependency><groupId>org.ow2.asm</groupId><artifactId>asm-commons</artifactId><version>7.0</version></dependency>//或者引入如下包asm-commons-7.0.jarasm-analysis-7.0.jarasm-tree-7.0.jarasm-7.0.jar

2. 定义一个ClassVisitor,功能是在所有方法调用前和调用后分别通过System.out.println打印一些信息

输入为字节码,输出也是字节码

//MyClassVisitor.javapublicclassMyClassVisitorextendsClassVisitor{privatestaticfinalTypeSYSTEM;privatestaticfinalTypeOUT;privatestaticfinalMethodPRINTLN;static{java.lang.reflect.Methodm=null;try{m=PrintStream.class.getMethod("println",newClass<?>[]{String.class});}catch(Exceptione){}SYSTEM=Type.getType(System.class);OUT=Type.getType(PrintStream.class);PRINTLN=Method.getMethod(m);}privateStringcName;publicMyClassVisitor(byte[]bytes){super(Opcodes.ASM7,newClassWriter(PUTE_FRAMES));newClassReader(bytes).accept(this,ClassReader.EXPAND_FRAMES);}Stringformat(Stringname){returnname.replaceAll("<","_").replaceAll("\\$|>","");}@Overridepublicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){cName=format(name);super.visit(version,access,name,signature,superName,interfaces);}@OverridepublicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){if((access&256)!=0){returnsuper.visitMethod(access,name,desc,signature,exceptions);}returnnewMyMethodAdapter(super.visitMethod(access,name,desc,signature,exceptions),access,name,desc);}publicbyte[]getBytes(){return((ClassWriter)cv).toByteArray();}classMyMethodAdapterextendsAdviceAdapter{privateStringmName;publicMyMethodAdapter(MethodVisitormethodVisitor,intacc,Stringname,Stringdesc){super(Opcodes.ASM7,methodVisitor,acc,name,desc);this.mName=format(name);}@OverrideprotectedvoidonMethodEnter(){getStatic(SYSTEM,"out",OUT);push(cName+"."+mName+"start");this.invokeVirtual(OUT,PRINTLN);}@OverrideprotectedvoidonMethodExit(intopcode){getStatic(SYSTEM,"out",OUT);push(cName+"."+mName+"end");this.invokeVirtual(OUT,PRINTLN);}}}

3. 定义一个简单的classLoader来加载转换后的字节码

//MyLoader.javaclassMyLoaderextendsClassLoader{privateStringcname;privatebyte[]bytes;publicMyLoader(Stringcname,byte[]bytes){ame=cname;this.bytes=bytes;}@OverrideprotectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{Class<?>clazz=null;if(clazz==null&&cname.equals(name)){try{clazz=findClass(name);}catch(ClassNotFoundExceptione){}}if(clazz==null){clazz=super.loadClass(name,resolve);}returnclazz;}@OverrideprotectedClass<?>findClass(Stringname)throwsClassNotFoundException{Class<?>clazz=this.findLoadedClass(name);if(clazz==null){clazz=defineClass(name,bytes,0,bytes.length);}returnclazz;}}

4. 加载转换Hello类,然后反向调用其方法

//将如下main函数加入MyClassVisitor.java中publicstaticvoidmain(String[]args)throwsException{try(InputStreamin=Hello.class.getResourceAsStream("Hello.class")){byte[]bytes=newbyte[in.available()];in.read(bytes);Stringcname=Hello.class.getName();Class<?>clazz=newMyLoader(cname,newMyClassVisitor(bytes).getBytes()).loadClass(cname);clazz.getMethod("test1").invoke(null);}}

5. 编译

java-cptools.jarcom.sun.tools.javac.Main-cpasm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:.*.java//或者javac-cpasm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:.*.java

5. 运行

java-cpasm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:.MyClassVisitor//结果如下:Hello.test1startHello.test2startinvoketest2Hello.test2endHello.test1end

asm的使用很广泛,最常用的是在spring aop里面切面的功能就是通过asm来完成的

4.2利用asm与Instrument制作调试工具

Instrument工具

Instrument类有如下方法,可以增加一个类转换器

addTransformer(ClassFileTransformertransformer,booleancanRetransform)

执行如下方法的时候,对应的类将会被重新定义

retransformClasses(Class<?>...classes)

与asm配合使用

当我们修改Agent.java代码为下面内容

//AgentpublicclassAgent{publicstaticvoidagentmain(Stringargs,Instrumentationinst){try{URLClassLoaderloader=(URLClassLoader)Agent.class.getClassLoader();Methodmethod=URLClassLoader.class.getDeclaredMethod("addURL",URL.class);method.setAccessible(true);//代码级引入依赖包method.invoke(loader,newFile("asm-7.0.jar").toURI().toURL());method.invoke(loader,newFile("asm-analysis-7.0.jar").toURI().toURL());method.invoke(loader,newFile("asm-tree-7.0.jar").toURI().toURL());method.invoke(loader,newFile("asm-commons-7.0.jar").toURI().toURL());inst.addTransformer(newClassFileTransformer(){@Overridepublicbyte[]transform(ClassLoaderloader,StringclassName,Class<?>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]bytes){returnnewMyClassVisitor(bytes).getBytes();}},true);inst.retransformClasses(Class.forName("Hello"));}catch(Exceptione){e.printStackTrace();}}}

编译并打包成agent.jar

//编译javac-cpasm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:.*.java//打包jar-cmfmanifest.mfagent.jarMyLoader.classMyClassVisitor.classMyClassVisitor\$MyMethodAdapter.classAgent.classAgent\$1.class

attach进程修改字节码

//执行java-cp.:tools.jarAttachMain3003//执行前后Hello进程的输出变化为invoketest2invoketest2invoketest2Hello.test1startHello.test2startinvoketest2Hello.test2endHello.test1endHello.test1startHello.test2startinvoketest2Hello.test2endHello.test1end

利用asm及instrument工具来实现热修改字节码现在有许多成熟的工具,如btrace(/btraceio/btrace,jvm-sandbox/alibaba/jvm-sandbox)

点击这里,了解更多精彩内容

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。