点滴记忆

搭建的站点

基于byteBuddy的java agent设计

2021-10-28 Saber

参考资料: java classLoader问题: https://segmentfault.com/a/1190000040364396

                 java agent基础原理:https://blog.csdn.net/ancinsdn/article/details/58276945?ivk_sa=1024320u

                  使用 javaagent 和 动态 Attach两种方式实现类的动态修改和增强: https://www.cnblogs.com/756623607-zhang/p/12575509.html
                  javaagent学习笔记: https://segmentfault.com/a/1190000016601560?utm_source=tag-newest


参考项目 : 基于bytebuddy的agent简单实现:https://gitee.com/yanghuijava/agent-tutorial
                  http-plugin:  https://github.com/alibaba/jvm-sandbox-repeater
                   完整版设计参考: https://github.com/apache/skywalking.git
https://gitee.com/beetle082/bee-apm.git

agent和attach模式
agent项目: https://github.com/jakubhalun/tt2016_byte_buddy_agent_demo.git
应用案例: https://github.com/jakubhalun/tt2016_byte_buddy_agent_demo_instrumented_app.git
attach命令:  https://github.com/jakubhalun/tt2016_byte_buddy_agent_loader.git
可以使用agent和attach两种模式。

classloader相关的地方: 类的继承、代码中使用class或者使用class初始化对象、对于传入的已经生成的对象无影响。

1 项目介绍

基于ByteBuddy开发的java探针工具,提供http、dubbo等协议的流量复制功能。
支持 JavaAgent 和 Attach 两种模式使用。
参考项目:apache-skywalking、jvm-sandbox-repeater。

2 开发注意事项

  • 尽量不要使用第三方jar包,常规基础包如apache.commons、guava、fastjson等可考虑使用。但必须使用maven-shade-plugin统一更改包路径,防止跟应用中的jar包冲突。
  • 不能使用log4j等日志框架,自己需实现log功能。
  • 字节码增强类需要打包成jar包,自定义classLoader来加载jar包,否则无法加载

3 使用方式

  • JavaAgent 模式

    java -javaagent:<agentJarPath>[=<agentOptions>] [-D<agentKey>=<agentValue>...] -jar <projectJarPath>

example: java -javaagent:agent-1.0.0-SNAPSHOT.jar -Dtraffic.agent.http.sample.rate=100 -Dtraffic.agent.http.ignore.ips=127.0.0.1,123.*.1.1 -jar springMvc-example.jar

  • Attach 模式

    java -jar <attachJarPath> <agentJarPath> <pid> [<agentOptions>]

example: java -jar attacher-1.0.0-SNAPSHOT.jar agent-1.0.0-SNAPSHOT.jar 3280 "traffic.agent.http.sample.rate=2;traffic.agent.http.ignore.ips=127.0.0.1,123.*.1.1"

4  系统参数

系统参数名
描述
默认值
traffic.agent.logging.dir 日志文件路径 logs/apicollection
traffic.agent.http.sample.rate HTTP采样率,代表每N个请求采样1次 100
traffic.agent.http.ignore.ips HTTP忽略的ip,格式为:127.0.0.1, 127.*, 127..12.22
traffic.agent.http.response.bytes.max HTTP响应体数据最大值限制,超过该值则不记录ResponseBody 204800 (200K)
traffic.agent.plugins.mount plugins默认路径 plugins, activations

系统参数优先级说明, Agent Options > Jvm Options > System Properties,容器变量认为是System Properties

5 支持的plugins

  • http-plugin

6 性能测试

6.1 http-plugin性能测试

测试接口: get请求,返回一个字符串
测试命令: ab -n100000 -c50 url(10万请求 50并发量)

  • 不使用agent,qps: 11765.16
  • 使用agent,qps: 10760.62


7 遇到的问题总结

7.1 java.lang.NoClassDefFoundError

该异常的主要 原因是classloader问题,agent运行的classloader为app classloader(默认),springboot的classloader为JarLauncher(parent为app classloader)。

因为双亲委派机制,如果agent使用了JarLauncher加载的class,会报上述错误。

解决方法: 将 使用JarLauncher加载的class的class文件打包成jar包,自己新建个AgentClassLoader(parent为JarLauncher)来加载jar包 和 创建对象。

7.2 agent无法attach问题

bytebuddy有2套annotation api来处理aop逻辑,一套是implemetaion下的注解,另一套是adivce api 。

解决方法: 使用advice api, 只有这套注解是支持attach的

7.3 agent如何detach

 ResettableClassFileTransformer resettable = new AgentBuilder.Default()
        .disableClassFormatChanges()
        //忽略classes的匹配规则(不拦截)
        .ignore(IgnoreTypes.INSTANCE.get())        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        .with(new AgentListener())
        //定义被拦截class的匹配规则
        .type(MatchTypes.INSTANCE.get(enhanceClassDefineList))        //拦截class做增强处理
        .transform(new Transformers(enhanceClassDefineList))        
         .installOn(inst); 
//attach模式agent卸载
if (isRuntimeAttach) {    ProcessWatcher.watch(AgentSpyApi.getAttacherPid())
            .checkIntervalMills(1000)
            .onShutdown(() -> {
                //agent卸载
                resettable.reset(inst, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
                //重置attach相关的值
                AgentSpyApi.destroy();
                System.out.println("TrafficCaptureAgent stopped successfully!");
                LOGGER.info("TrafficCaptureAgent stopped successfully!");
            }).start();
} 

resettable.reset(inst, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);  

触发机制: 监听attacher进程号

7.4 advice api使用坑

绑定参数案例

newBuilder.visit(Advice.withCustomMapping()
                .bind(AInterceptorClass.class, methodsInterceptPoint.interceptorClass())
                //拦截器代理
                .to(MethodsInterceptorDelegator.class)                //被拦截的方法匹配规则
                .on(methodsInterceptPoint.methodMatcher())); 


具体使用案例:
@Advice.OnMethodEnter
public static InterceptorContext enter(@Advice.This Object target,
                                       @Advice.Origin Method method,
                                       @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] allArguments,
                                       @AInterceptorClass String interceptorClass) {    InterceptorContext context = new InterceptorContext(target, method, allArguments, method.getParameterTypes());
    try {        MethodsInterceptor interceptor = InterceptorClassLoader.load(interceptorClass,
                Thread.currentThread().getContextClassLoader());
        interceptor.beforeMethod(context);
        allArguments = context.getArguments();
    } catch (Throwable t) {        LOGGER.error(t, "class[%s] before method[%s] intercept failure", target.getClass(), method.getName());
    }    return context;
}

@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void exit(@Advice.Enter InterceptorContext context,
                        @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object ret,
                        @Advice.Thrown Throwable thrown,
                        @AInterceptorClass String interceptorClass) {    try {
        MethodsInterceptor interceptor = InterceptorClassLoader.load(interceptorClass,
                Thread.currentThread().getContextClassLoader());
        Object newRet = ret;
        interceptor.afterMethod(context, newRet, thrown);
        ret = newRet;
    } catch (Throwable t) {        LOGGER.error(t, "class[%s] after method[%s] intercept failure", context.getObjInst().getClass(),
                context.getMethod().getName());
    }}


  @Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] allArguments readOnly=false 代表可修改该对象;
typing = Assigner.Typing.DYNAMIC 不用指定具体类型来接收参数,每次获取到的allArguments是一份浅拷贝数据

标签: java agent bytebuddy http

评论(0) 浏览(60)

maven lifeCycle

2021-8-2 Saber

标签: maven

评论(0) 浏览(100)

使用planuml分析springboot、springCloud、SpringCloudAlibaba的pom依赖关系

2021-7-29 Saber

springboot

@startuml

hide empty circle
hide empty members
skinparam shadowing false

rectangle "spring-boot-build:2.2.14.RELEASE" as bootBuild #lightgreen
rectangle "spring-boot-dependencies:2.2.14.RELEASE" as bootDependencies #lightgreen
rectangle "spring-boot-parent:2.2.14.RELEASE" as bootParent #lightgreen

rectangle "spring-boot:2.2.14.RELEASE" as bootCore #skyblue
rectangle "spring-boot-actuator:2.2.14.RELEASE" as bootActuator #skyblue
rectangle "spring-boot-starters:2.2.14.RELEASE" as bootStarters #skyblue
rectangle "spring-boot-devtools:2.2.14.RELEASE" as bootDevtools #skyblue
rectangle "..." as bootParentOther #skyblue

rectangle "spring-boot-starter:2.2.14.RELEASE" as bootStarter
rectangle "spring-boot-starter-activemq:2.2.14.RELEASE" as bootStarterActivemq  #yellow
rectangle "spring-boot-starter-aop:2.2.14.RELEASE" as bootStarterAop   #yellow
rectangle "..." as starterOther

bootBuild <|-- bootDependencies
bootDependencies <|-- bootParent

bootParent <|-- bootCore
bootParent <|-- bootActuator
bootParent <|-- bootDevtools
bootParent <|-- bootStarters
bootParent <|-- bootParentOther

bootStarters <|-- bootStarter
bootStarters <|-- bootStarterActivemq
bootStarters <|-- bootStarterAop
bootStarters <|-- starterOther

bootStarter ..> bootCore

bootStarterActivemq ..> bootStarter
bootStarterAop ..> bootStarter

@enduml

springCloud

@startuml

hide empty circle
hide empty members
skinparam shadowing false

rectangle "spring-boot-dependencies:2.2.14.RELEASE" as bootDependencies #pink
rectangle "spring-cloud-build-dependencies:2.2.14.RELEASE" as dependencies #pink

rectangle "spring-cloud-build:2.2.14.RELEASE" as build #pink
rectangle "spring-cloud-dependencies-parent:2.2.14.RELEASE" as dependenciesParent #pink

rectangle "spring-cloud-gateway-dependencies:2.2.14.RELEASE" as gatewayDependencies #lightgreen
rectangle "spring-cloud-gateway:2.2.14.RELEASE" as buildGateway #lightgreen

rectangle "spring-cloud-gateway-core:2.2.14.RELEASE" as gatewayCore
rectangle "spring-cloud-starter-gateway:2.2.14.RELEASE" as gatewayStarter #yellow
rectangle "spring-cloud-gateway-mvc:2.2.14.RELEASE" as gatewayMVC
rectangle "spring-cloud-gateway-sample:2.2.14.RELEASE" as gatewaySample
rectangle "spring-cloud-gateway-server:2.2.14.RELEASE" as gatewayServer
rectangle "spring-cloud-gateway-webflux:2.2.14.RELEASE" as gatewayWebflux

rectangle "spring-cloud-commons-dependencies:2.2.14.RELEASE" as cloudCommonsDependencies #pink
rectangle "spring-cloud-netflix-dependencies:2.2.14.RELEASE" as cloudNetflixDependencies #pink
rectangle "spring-cloud-circuitbreaker-dependencies:2.2.14.RELEASE" as cloudCircuitbreakerDependencies #pink


bootDependencies <|-- dependencies
build <|-- buildGateway
dependenciesParent <|-- gatewayDependencies
buildGateway <|-- gatewayCore
buildGateway <|-- gatewayMVC
buildGateway <|-- gatewaySample
buildGateway <|-- gatewayServer
buildGateway <|-- gatewayWebflux
buildGateway <|-- gatewayStarter

build o-- dependencies #gray
gatewayDependencies --o buildGateway  #gray
cloudCommonsDependencies --o buildGateway #gray
cloudNetflixDependencies --o buildGateway #gray
cloudCircuitbreakerDependencies --o buildGateway #gray

@enduml

SpringCloudAlibaba

@startuml

hide empty circle
hide empty members
skinparam shadowing false

rectangle "spring-cloud-build:2.3.1.RELEASE" as cloudBuild #pink
rectangle "spring-cloud-alibaba:2.2.6-SNAPSHOT" as cloudAlibaba #lightgreen
rectangle "spring-boot-dependencies:2.3.2.RELEASE" as bootDependencies #lightblue
rectangle "spring-cloud-dependencies:Hoxton.SR9" as cloudDependencies #pink
rectangle "dubbo-dependencies-bom:2.7.8" as dubboDependencies #pink

rectangle "spring-cloud-dependencies-parent:2.3.1.RELEASE" as cloudDependenciesParent #pink
rectangle "spring-cloud-alibaba-dependencies:2.2.6-SNAPSHOT" as cloudAlibabaDependencies #lightgreen
rectangle "spring-cloud-alibaba-starters:2.2.6-SNAPSHOT" as cloudAlibabaStarter #grey
rectangle "spring-cloud-starter-dubbo:2.2.6-SNAPSHOT" as starterDubbo #yellow
rectangle "spring-cloud-starter-alibaba-nacos-discovery:2.2.6-SNAPSHOT" as starterNacosDiscovery #yellow
rectangle "..." as starterOther #yellow

rectangle "spring-cloud-alibaba-examples:2.2.6-SNAPSHOT" as alibabaExamples
rectangle "nacos-config-example:2.2.6-SNAPSHOT" as nacosConfigExamples
rectangle "spring-cloud-alibaba-dubbo-examples:2.2.6-SNAPSHOT" as dubboExamples
rectangle "..." as exampleOther

cloudBuild <|-- cloudAlibaba
cloudDependenciesParent <|-- cloudAlibabaDependencies
cloudDependenciesParent <|-- cloudDependencies

cloudAlibabaDependencies --o cloudAlibaba #gray
bootDependencies --o cloudAlibaba #gray
cloudDependencies --o cloudAlibaba #gray
dubboDependencies --o cloudAlibaba #gray

cloudAlibaba  <|-- cloudAlibabaStarter
cloudAlibabaStarter <|-- starterDubbo
cloudAlibabaStarter <|-- starterNacosDiscovery
cloudAlibabaStarter <|-- starterOther
cloudAlibaba  <|-- alibabaExamples
alibabaExamples  <|-- nacosConfigExamples
alibabaExamples  <|-- dubboExamples
alibabaExamples  <|-- exampleOther

@enduml


标签: java springboot springcloud planuml

评论(0) 浏览(83)

JVM排查问题

2021-7-28 Saber


CPU占用飙升

1. top 找到CPU占用最大的进程 (shift+c按CPU占用量排序)

2. top -Hp pid 找到进程中CPU占用最大的线程

3. printf '%x' ThreadId 将线程ID转换成十六进制

4. jstack pid|grep 'ThreadId' -C5 -color 打印堆栈信息


查找死锁

1. jps -l

2. jstack -l pid 打印堆栈信息

内存泄漏

1. jmap -head pid 查看内存使用情况



2. jstat -gcutil pid 1000 查看GC情况

3. jmap -histo:live pid|more 查看对象内存占用情况(执行之后,会造成jvm强制执行一次GC,线上谨慎使用

Memory Analyzer Tool (MAT)

1. jmap -dump:format=b,file=/root/dump.hprof pid 导出堆快照文件
 
2. 用MAT进行分析

minor gc、major gc、full gc执行频率,计算时间占比。





其他:

1.1 Jstat 查看堆内存、元数据各部分的使用量,以及加载类的数量。

1.2 Jinfo 用来查看正在运行的java应用程序的扩展参数(JVM中-X标示的参数),甚至支持在运行时修改部分参数。

1.3 Jstack 用于查看线程状态。

1.4 Jmap JVM Memory Map命令用于生成heap dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

1.5 Jhat (web版本的MAT)配合Jmap导出的dump文件使用。jhat内置http server,用hql语法查看dump文件。

1.6 Jprofier 分析内存分布情况,定位内存泄漏等问题。

标签: java jvm

评论(0) 浏览(100)