本文字数:5232字
预计阅读时间:25分钟
01
引言
1.1背景
性能优化是一个Java程序员的必备技能,但是定位性能瓶颈或者是问题点是一个费时又费力的事情。在我们的实际项目中就碰到了这样的烦恼,某业务的一个接口,平均耗时很短,但是总有约1%的流量,波动较大。多次review代码,并没有发现明显的问题,查看业务日志和三方调用日志也没有明显的问题。所以定位小概率的耗时长成为了的关键。
1.2性能优化
定位问题首先想到的就是添加日志,但是在业务复杂的系统中,要想通过添加日志来定位问题,那就需要添加大量的日志,会陷入到加日志-重启服务-加日志的循环中。不仅导致代码混乱,服务不断重启更是不可接受的。Skywalking配合Arthas是一个比较好的定位问题的手段。使用Skywalking定位少数耗时长的接口,时间大概消耗在哪块,然后通过Arthas动态监控、添加日志和修改部分业务代码,来实时验证自己的优化结果。
02
原理分析
为了更好地理解问题定位过程,我们深入分析一下Skywalking和Arthas的原理。Skywalking和Arthas共同使用的是Instrumentation JavaAgent机制、以及基于ASM为基础的字节码框架。其中Arthas还使用了Attach API 、OGNL等技术。下面我们从类加载机制、javaAgent机制、字节码技术、Arthas的实现四个方面详细阐述下。
2.1类加载机制
要理解类的动态修改和加载,我们先简单介绍下类的加载机制,这部分可能大家都比较熟悉,下图是一个完整的JVM系统。一个java类,通过编译生成class文件,然后class文件被类加载系统加载到java虚拟机中:
当jvm要加载某个类时,jvm会先指定一个类加载器,负责加载此类。而此指定的类加载器在尝试自己去根据某个类的二进制名字查找其相应的字节码文件并加载之前,会首先委托给其父亲(getParent方法返回的类加载器)尝试加载,如果加载失败,就会由自己来尝试加载此类。一般情况下,这个由jvm指定的类加载器就是AppClassLoader,jvm会自动调用其loadClass(String name)方法来开启类的加载过程(真正完成类加载工作的是通过调用defineClass方法来实现的),具体细节如下图:
不过无论是什么ClassLoader,它的根父类都是 java.lang.ClassLoader。loadClass()方法最终会调用到 ClassLoader.defineClass1()中,这是一个Native方法:
在openjdk中查看源码,definClass1()对应的方法在ClassLoader.c文件中的Java_java_lang_ClassLoader_defineClass1():
Java_java_lang_ClassLoader_defineClass1 主要是调用了JVM_DefineClassWithSource()加载类,继续跟踪,会发现最终调用的是jvm.cpp中的jvm_define_class_common()方法。该逻辑主要就是利用ClassFileStream将要加载的class文件转成文件流,然后调用SystemDictionary::resolve_from_stream(),生成Class在JVM中的代表:Klass。对于Klass,就是JVM用来定义一个Java Class的数据结构。不过Klass只是一个基类,Java Class真正的数据结构定义在InstanceKlass中。
InstanceKlass中记录了一个Java类的所有属性,包括