性能文章>MProfiler技术架构>

MProfiler技术架构原创

https://a.perfma.net/img/2382850
11月前
70011

MProfiler是单机Java应用一键检测利器,其主要的技术架构如下图所示.

MProfiler为了减少对目标Java进程的干扰,对目标Java进程只是采集数据,对数据的分析以及风险库诊断都要在独立的MProfiler进程中执行。

MProfiler在分析目标Java应用程序时,由于JVMTIAgent和JavaAgent最终是需要在目标Java进程中运行的,所以可以在启动时加载,也可以在稍后的某个时间点加载。

(1)要在启动时加载,需要在命令行中添加VM参数,如下:

启动时加载时,能够获取到一些更准确的信息,如JVMTIAgent如果在启动时加载,那么所有类的加载和卸载,线程的创建和销毁等信息就会同步到MProfiler中。另外有些事件只局限于启动时注册监听,如异常事件,所以获取应用异常的API接口可能只在启动时加载代理这种方式下有效。

(2)在运行时加载,这种方式在启动MProfiler时,当选择目标Java进程后,MProfiler会动态进行Attach操作,也就是说不需要额外的配置操作。

运行时加载可能会出现CPU短暂飙高的现象。大部分用户可能只在系统出现问题时,才会使用MProfiler。这时候如果选择用启动时加载方式,还要再次重现问题,而使用运行时加载,可直接对当前已经出现的问题进行分析。

 

1、API接口

API接口用来获取目标应用程序的各种数据。这些API接口要兼顾轻量级、稳定性和兼容性。其中的轻量级指的是,在尽可能不打扰用户应用程序的情况下获取数据。而兼容性是要在不同的JDK版本,不同的虚拟机类型上能够返回友好的信息,操作系统兼容性目前没有考虑,只专注于支持Linux下64位版本。

 API接口是MProfiler的基础,只有这些API接口足够多,足够好,才能实现丰富的风险库。

举一个API接口实现的例子,这个API能够获取某个类的所有子类,我们在实现时,一般有3种方式:

(1)查找目标应用程序使用的代码,最常见的就是jar包,对这些jar包进行解析,解读出类的继承体系。优点就是完全不打扰用户的应用程序,缺点是实现比较麻烦,初次分析时,运算量比较大。还有最主要的一个缺点就是,当用户对类执行加密操作时,这种方法行不通,不过仍然可以利用代理技术,将已经加载到虚拟机中的所有类导出来,在结束排查时删除用户源代码,防止泄漏;

(2)利用代理技术,直接调用现成的API接口从用户的应用程序中查找。优点是实现相对简单一些,结果比较实时,缺点是会打扰用户的应用程序;

(3)如果导出了堆文件,那么通过堆文件也能得到类的继承关系,缺点是信息不全,能获取的其它信息很有限。

考虑到后续还需要从源代码中提取各种信息,如当前类是否调用了某个特定的方法,类中是否覆写了finalize()方法等信息,最终选择了第一种实现方式。 

 

2、风险库

风险项需要调用API接口获取足够多的数据才能提高诊断的准确性。风险项越多,涉及到的类别越广,准确圈定应用问题的机率就越大。

举一个风险库实现的例子。我们要检查用户应用程序中数组的使用情况,主要检查一些不好的使用数组的习惯,如数组长度为0或1,数组中没有存储有效的数据,数组很大,但是存储的有效数据却很少这些情况。

首先要获取所有的数组实例,API接口目前提供了2个获取某个类型下的所有实例的接口:

(1)通过JVMTI实现,可实时获取到最新数据,如果某个类型下的实例比较多,会严重干扰用户应用程序;

(2)先导出堆文件,从离线的堆文件中获取。

这里会选择第2个API接口,因为没有办法保证某个类型下实例的数量。在得到所有数组实例后,需要获取这些实例对应的数组的具体信息,仍然从堆文件提供的接口中找到每个数组的具体内容,然后通过数组长度和有效数据的数量来完成风险项。

在这个风险项的实现过程中,还需要做一些额外的工作。

为了能更准确的结合到上下文进行提示,MProfiler还计划实现集合和字符串的风险项,集合的风险项检测和数组差不多,就是对于集合长度为1,集合元素过多,集合容量过大而有效数据过少等情况进行提示;字符串主要是检查出现大量重复字符串的情况。这2个风险项的底层实现都会涉及到数组,如字符串本来就是用字符数组实现的,所以我们在进行数组检测时,要将这些数组排除在外,防止重复提示。

对于字符串来说,可以读取所有的java.lang.String类的实例,然后遍历这些实例,根据提供的API接口读取每个实例的value值,也就是字符数组,将这些值排除在数组实例的检测之外。

对于集合来说,实现有一些复杂,我们需要获取List,Map和Set等接口的实现子类,然后遍历这些子类的所有字段值,如果某个字段的值为数组,就将这个数组排除在数组实例的检测之外。

 

点赞收藏
分类:标签:
鸠摩

著有《深入解析Java编译器:源码剖析与实例详解》、《深入剖析Java虚拟机:源码剖析与实例详解》等书籍。公众号“深入剖析Java虚拟机HotSpot”作者

请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

为什么要做代码Review?

为什么要做代码Review?

日常Bug排查-集群逐步失去响应

日常Bug排查-集群逐步失去响应

浅析AbstractQueuedSynchronizer

浅析AbstractQueuedSynchronizer

1
1