`
beck5859509
  • 浏览: 108867 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

ASM

 
阅读更多
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"于公司jvm智能分析平台的思考

引子
在讲述主题之前,先看一下之前遇到的几个问题
1、问题一
2、问题二
3、问题三
url1
url2
url3


目前公司缺少一个jvm智能分析平台,在遇到一些比较难定位的问题时,往往只能查看日志,或者临时添加日志,但也会带来一些麻烦,比如日志量过大,进程重启问题不必现等,又或者只能反复jstack进行问题猜测,但效果也不理想。

jvm智能分析平台的特点。
1、动态加载,目标程序无需重启
2、字节码编程、性能得到保障
3、web平台化操作
3、目标方法的入参、异常、返回结果的分析
4、目标方法内部每一行代码的hao时的分析
5、指定java实例数统计及size分析
3、与第三方jar进行隔离,代码无污染
4、对线程栈做快照对线程

demo展示

冰山一角

可行性
已和运维组勇哥沟通,并无相关的平台,只是简单的组件的监控

相关技术
/java/jni/jvmti/bytecode/c/c++



1、统计指定的java实例数,并可以统计每个实例的大小(size)
2、查找最高耗时的方法(小型程序)
3、拉取git代码进行web在线调试
4、采集到与JVM 有关的大量信息,例如内存、线程、JVM 崩溃等
5、对线程栈做快照
6、Java Agent受jvm本身的影响

总结
Native Agent因为工作原理的不同,导致其与Java Agent相比,拥有明显的优势,具体总结如下:
1. 获取JVM运行时的性能参数。
2. 获取JVM线程方法调用栈信息
3. 不受JVM的运行状态影响。
4. 开销更少

visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |
visitLocalVariable | visitLineNumber )*
visitMaxs )?
visitEnd


Btrace (Byte Trace)是sun推出的一款Java 动态、安全追踪(监控)工具,可以在不停机的情况下监控系统运行情况,并且做到最少的侵入,占用最少的系统资源。。BTrace在使用上做了很多限制,如不能创建对象、不能使用数组、不能抛出或捕获异常、不能使用循环、不能使用synchronized关键字、脚本的属性和方法都必须使用static修饰等,具体限制条件可参考用户手册。根据官方声明,不当地使用BTrace可能导致JVM崩溃,如BTrace使用错误的.class文件,所以,可以先在本地验证BTrace脚本的正确性再使用



https://blog.csdn.net/coslay/article/details/43113029
https://www.cnblogs.com/163yun/p/10078137.html

asm:
0:   aload_0
首先第一个0代表虚指令中的行号(后面会应到,确切说应该是方法的body部分第几个字节),每个方法从0开始顺序递增,但是可以跳跃,跳跃的原因在于一些指令还会接操作的内容,这些操作的内容可能来自常量池,也可以标志是第几个slot的本地变量,因此需要占用一定的空间。

aload_0指令是将“第1个”slot所在的本地变量推到栈顶,并且这个本地变量是引用类型的,相关的指令有:aload_[0-3](范围是:0x2a ~ 0x2d)。如果超过4个,则会使用“aload + 本地变量的slot位置”来完成(此时会多占用1个字节来存放),前者是通过具体的几个指令直接完成。

许多地方会解释为第1个引用类型的本地变量,但胖哥是一个逻辑怪,认为这句话有问题,并不是第1个引用变量,普通变量如果在它之前,它也不是第1个了,此时本身就是第1个本地变量,更确切地说是第一个slot所在位置的本地变量。



注意:

常量池的存放内容

存放所有的方法名

field名

方法签名(方法参数+返回值)

类型名

class文件中的常量值

常量池的前四部分可以称作是符号引用(即只有一些名称,但没有实际的地址,在运行期进行类的加载过后,会为这些东西分配实际的内存,到时候符号引用就会转化为直接引用,就能被JVM用了)

常量池的组成:符号引用、常量(这个常量包含我们代码中定义的常量,eg、字符串常量,也包括class文件中的常量,eg.SourceFile)。

主版本号的对应(eg.50对应jdk6,51对应jdk7),查看《深入理解java虚拟机(第二版)》P167

Stack:操作数栈的深度(这个值就是类加载阶段为操作数栈分配的深度)

Locals:局部变量的分配空间(单位是slot,不是个数),对于double和long这两个64bit的,需要两个slot,对于其他<=32bit的,只需要一个slot

Args_size:方法参数的个数,包括方法参数、this(this只针对实例方法,static方法不会自动添加this)

inc()方法:我详细注释了该方法的执行过程,这也就是JVM执行一个方法的基本流程(基于栈)


ClassReader.java

        // visits the class declaration
        classVisitor.visit(readInt(items[1] - 7), access, name, signature,
                superClass, interfaces);

        // visits the source and debug info
        if ((flags & SKIP_DEBUG) == 0
                && (sourceFile != null || sourceDebug != null)) {
            classVisitor.visitSource(sourceFile, sourceDebug);
        }

        // visits the module info and associated attributes
        if (module != 0) {
            readModule(classVisitor, context, module,
                    moduleMainClass, packages);
        }
       
        // visits the outer class
        if (enclosingOwner != null) {
            classVisitor.visitOuterClass(enclosingOwner, enclosingName,
                    enclosingDesc);
        }

        // visits the class annotations and type annotations
        if (anns != 0) {
            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitAnnotation(readUTF8(v, c), true));
            }
        }
        if (ianns != 0) {
            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitAnnotation(readUTF8(v, c), false));
            }
        }
        if (tanns != 0) {
            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), true));
            }
        }
        if (itanns != 0) {
            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), false));
            }
        }

        // visits the attributes
        while (attributes != null) {
            Attribute attr = attributes.next;
            attributes.next = null;
            classVisitor.visitAttribute(attributes);
            attributes = attr;
        }

        // visits the inner classes
        if (innerClasses != 0) {
            int v = innerClasses + 2;
            for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
                classVisitor.visitInnerClass(readClass(v, c),
                        readClass(v + 2, c), readUTF8(v + 4, c),
                        readUnsignedShort(v + 6));
                v += 8;
            }
        }

        // visits the fields and methods
        u = header + 10 + 2 * interfaces.length;
        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
            u = readField(classVisitor, context, u);
        }
        u += 2;
        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
            u = readMethod(classVisitor, context, u);
        }

        // visits the end of the class
        classVisitor.visitEnd();



























引子



    在正式讲述本文主题前,先看一下之前项目中遇到的几个问题



    问题一

        问题现象:18年12月底的时候线上有一台手机在 18:00~21:00之间频繁连接和断开连接

        分析步骤:

        1、客户端同事初步分析手机日志,发现有断开异常,看调用堆栈是业务代码抛出

        image.png



        2、联想到后台主动关闭连接时,不会写入mqtt 消息,所以输入流stream读不到数据属于正常现象。

             另外问题发生时的日志已经被清除,只能查看客户端的其它日志,幸运的是当时有网络包可以分析

        3、查看网络请求终于发现一些端倪:

            image.png

            image.png

            客户端先是连接101.132.150.36,同时后台139.196.100.216断开客户端连接(18:00:53前的连接),18:01分客户端又继续连接139.196.100.216,形成循环连接的现象。

            重点查看连接connack的返回结果,找到了突破口。

            image.png

         4、第二个红框值为3,代表需要重新注册。因个别手机在多用户场景会出现连接切换重连的情况,后台发现token不匹配就会进行重连,客户端已经针对此情况修复。



    问题二

        问题现象:5月初收到ups-receiver线程池任务队列阻塞的监控报警

        分析步骤:

        1、查看错误日志,并没有找到相应的线索,也没有异常的日志。

        2、因为当天凌晨12点半发现,临时重启解决问题。

        3、对比内外部的receiver,单独拆分了回执线程池处理,并发布上线观察。

        4、果然第二天问题还是出现,打印jstack进行分析。

        5、问题得到确认,回执写入mq时的线程被阻塞,联系中间件同事进行处理,问题得以解决。

            image.png



    其它烧脑问题

    http://km.vivo.xyz/pages/viewpage.action?pageId=28500755

    http://km.vivo.xyz/pages/viewpage.action?pageId=16279250

    http://km.vivo.xyz/pages/viewpage.action?pageId=14488732



    当前痛点:

     梳理以上问题时都存在一个共同特点,业务在遇到一些比较难定位的问题时,往往只能查看日志,或者临时添加日志,但这也会带来一些麻烦,比如日志量过大,

     进程重启问题不必现,需要临时发布版本(可能引入其它风险)等,或者只能反复jstack进行问题猜测,但效果都不理想。当然针对不同层次需求场景,有对应

     的工具,比如内存占用过多,得不到释放,可以dump出文件,但是分析此文件也并不容易,有一定的成本。还有类似jconsole等工具,但已有的这些工具并不能

     满足高级的应用场景,比如动态查看方法的入参/返回结果/某个成员变量的值就可以很快定位出问题。



     解决方案:

        一个比较好的解决方案是建设一个公司级的JVM智能分析平台 --- 开发人员可以根据不同场景输入参数快速定位问题,提前监测,并协助智能分析问题发生的根因。

        jvm智能分析平台具备以下特点。

        1、动态加载,目标程序无需重启

        2、字节码编程、性能得到保障

        3、web平台化操作

        4、对目标方法的入参、异常、返回结果的分析

        5、对目标方法内部代码的耗时的分析

        6、对指定java实例数统计及size分析

        7、与第三方jar进行隔离,代码无污染

        8、对线程堆栈智能分析可能的问题原因



   冰山一角

    jdk1.5以后,jdk整合了一套有关虚拟机的JVMTI接口,可以供使用者调用,JVMTI的功能非常丰富,包含了访问虚拟机中线程/内存/堆/栈/类/方法/变量的方法。

    开发者可以灵活使用这些特性,开发出很多高级功能,比如上述提到的功能,不过这些接口是C/C++的,实现起来有一定的难度。以下例举两个例子。



    例一:

    比如下面展示了一个比较有趣的例子---通过java类名查找进程中对应实例的数量,具本如下。

    1、java端的统计调用功能由一个本地方法实现,通过jni的方式嵌入jvm中,具体实现通过c++代码去实现

    image.png

  

    2、监控程序启动时开启jvm统计对象数的功能

    image.png

    3、使用jvmit的方法迭代对象size及实例个数

    image.png

    4、最终结果,已之前java代码预期一致

    image.png



    除了通过jvmti实现上述功能(实现成本偏高),jdk还提供了instrument技术,可通过字节码转换进行实现相同的功能,并且功能更强大,

    除了实现监测,还可以动态添加新的功能:)



    例二:

    这里例举给目标方法动态添加try/catch及一个局部变量int i = 20的例子,当然也可以增加耗时统计

    1、Student类中最初的say方法

    image.png

    编译后Student的say方法字节码:

    image.png

    2、对目标方法进行添加字节码(部分代码)

    image.png

    3、编译后say方法的字节码

    image.png

   

    查看上图汇编后的字节码文件,istore_1已经将栈顶的int值20保存到了局部变量中,并且增加了try/catch的异常捕获

    总结:字节码编程方式同样可以动态实现目标程序的变更,但底层也通过jvmti进行实现的,但是通过字节码简单化了jvmti的开发工作量,当然为了更好的理解

              是有必要同时掌握jvmti指令及字节码指定的。以上只是字节码编程的冰山一角,这里不展开介绍了。



    以下是 jvm智能分析平台架构图



   image.png   



    该系统后期还可以衍生其它更强大的功能:比如通过web端拉取git代码在线调试线上环境(支付宝目前已有类似的功能)

 

    成本:

        关于系统人力评估(正式加两个外包-前端和后端各一人)

    收益 :

        1、智能分析系统,提前发现疑难、隐藏较深的问题,减少线上问题造成的损失。

        2、节省定位问题成本,现在项目越来越多,系统逻辑也越来越复杂,通过该系统快速锁定问题原因。
分享到:
评论

相关推荐

    asm.jar各个版本

    asm-1.3.3.jar, asm-1.3.4.jar, asm-1.3.5.jar, asm-1.4.1.jar, asm-1.4.2.jar, asm-1.4.3.jar, asm-1.4.jar, asm-1.5.1.jar, asm-1.5.2.jar, asm-1.5.3.jar, asm-2.0.jar, asm-2.1.jar, asm-2.2.1-sources.jar, asm...

    各种oracleasm rpm包(Linux下配置ASM使用)

    包含如下oracleasm包: kmod-oracleasm-2.0.6.rh1-3.el6.x86_64.rpm oracleasm-2.0.8-4.el6_6.src.rpm oracleasm-2.0.8-6.el6_7.src.rpm oracleasm-2.0.8-8.el7.src.rpm oracleasm-2.0.8-15.el7.centos.src.rpm ...

    asm-9.1-API文档-中文版.zip

    赠送jar包:asm-9.1.jar; 赠送原API文档:asm-9.1-javadoc.jar; 赠送源代码:asm-9.1-sources.jar; 赠送Maven依赖信息文件:asm-9.1.pom; 包含翻译后的API文档:asm-9.1-javadoc-API文档-中文(简体)版.zip; ...

    redhat/centos6.9 kmod-oracleasm/oracleasm-support/oracleasm rpm包

    kmod-oracleasm-2.0.8-15.el6_9.x86_64 oracleasm-support-2.1.8-1.el6.x86_64 oracleasmlib-2.0.4-1.el6.x86_64 安装顺序: rpm -ivh kmod-oracleasm-2.0.8-15.el6_9.x86_64.rpm rpm -ivh oracleasm-support-2.1.8...

    盈高入网要求规范管理系统ASM6000产品说明书V1.1.pdf

    盈高入网要求规范管理系统ASM6000产品说明书V1.1.pdf盈高入网要求规范管理系统ASM6000产品说明书V1.1.pdf盈高入网要求规范管理系统ASM6000产品说明书V1.1.pdf盈高入网要求规范管理系统ASM6000产品说明书V1.1.pdf盈高...

    ASM实例+ASM数据库安装(Win8+Ora10)

    ASM实例+ASM数据库安装(Win8+Ora10) 1 第一篇 创建未格式化的磁盘分区 1 1.1 打开压缩卷窗口 1 1.2 输入卷大小 3 1.3 选择挂载目录 4 1.4 格式化分区选项 5 1.5汇总信息 6 1.6分区完成后磁盘情况 7 1.7挂载目录...

    EditPlus(附asm.acp,asm.stx)

    EditPlus是很好用的编辑软件 但网上下的很多EP版本需要自己添加asm.acp,asm.stx,不是很方便 这个里面附带了asm.acp,asm.stx

    asm-util.jar

    asm-util-1.3.4.jar, asm-util-1.3.5.jar, asm-util-1.4.1.jar, asm-util-1.4.3.jar, asm-util-1.5.1.jar, asm-util-1.5.2.jar, asm-util-1.5.3.jar, asm-util-2.0.jar, asm-util-2.1.jar, asm-util-2.2.1-sources....

    oracle-rac使用的asmlib的kmod-oracleasm*.RPM包集合 6.6-7.5全集

    kmod-oracleasm-*,oracleasmlib*,oracleasm-support*。 虽然现在oracle-rac基本上已经都是用UEDV的来固化UUID了但是有些同学还不太会,然后看的教程都是使用asmlib来制作的,但是自己上机以后发现根本装不上啊,...

    asm操作指南(中文)

    ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class...

    cglib包及依赖汉cglib3.1和asm4.2

    cglib包及依赖汉cglib3.1和asm4.2,主要作用是用户代理,代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。...

    Oracle 10.2 ASM 最佳实践 最终版本

    Oracle 10.2 ASM 最佳实践 ASM installation ASM SGA and parameter sizing ASM and privileges ASMLIB Disks ASM and Multipathing DiskGroups Diskgroups and databases ASM redundancy and Failure Groups New ...

    asm4-guide.pdf

    Java的asm文档,来自官方。asm4-guide.pdf。ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。...

    开发工具 asm-5.1

    开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具 asm-5.1开发工具...

    汇编工具ASM汇编工具ASM汇编工具ASM汇编工具ASM

    汇编工具ASM汇编工具ASM汇编工具ASM汇编工具ASM汇编工具ASM

    ASM1061原理图

    ASM1061 原理图 PCIE转2 port Sata芯片 ASM1061 原理图 PCIE转2 port Sata芯片

    asm-5.0.4-API文档-中文版.zip

    赠送jar包:asm-5.0.4.jar; 赠送原API文档:asm-5.0.4-javadoc.jar; 赠送源代码:asm-5.0.4-sources.jar; 赠送Maven依赖信息文件:asm-5.0.4.pom; 包含翻译后的API文档:asm-5.0.4-javadoc-API文档-中文(简体)版...

    oracle不使用oracleasm的包配置ASM磁盘配置方法

    oracle不使用oracleasm的包配置ASM磁盘配置方法

    asm5.0 源码+demo+doc

    ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class ...

    ASM4使用指南

    ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。ASM提供与其他Java字节码...

Global site tag (gtag.js) - Google Analytics