在线调试工具 Arthas

22 篇文章 3 订阅
订阅专栏

Arthas(阿尔萨斯) 是 Alibaba开源的一款 Java在线诊断工具,能够分析,诊断,定位Java应用问题,例如:JVM信息,线程信息,搜索类中的方法,跟踪代码执行,观测方法的入参和返回参数等等。并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法的出入参,异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。简单的话:就是再不重启应用的情况下达到排查问题的目的。


当遇到以下类似问题而束手无策时,Arthas 可以帮助你解决:
【1】这个类从哪个 jar 包加载的? 为什么会报各种类相关的 Exception?
【2】无法确定线上环境是否是最新提交的代码,只能把服务器上的class文件下载下来使用反编译工具打开确认?
【3】遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
【4】线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
【5】出问题的方法被执行的路径非常多,无法确定该方法是在哪些具体的地方被调用或执行,这个方法也可能是第三方的 jar包里的。
【6】是否有一个全局视角来查看系统的运行状况?
【7】有什么办法可以监控到 JVM的实时运行状态?

一、本地安装


运行环境要求:Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
执行如下命令:使用 curl命令从阿里服务器上下载对应的 arthas jar包,window和 Linux下均可执行。

C:\Users\86156>curl -O https://alibaba.github.io/arthas/arthas-boot.jar
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  133k  100  133k    0     0   1496      0  0:01:31  0:01:31 --:--:--  1707

启动:使用 java -jar 启动 arthas-boot.jar,来安装 arthas,大约 10M。需要注意在启动之前,必须启动至少一个 java程序,否则会自动退出。运行此命令会发现 java进程,输入需要 attach 粘附进程对应的序列号,例如,输入1按回车。则会监听该进程。

d:\DevSoft>java -jar arthas-boot.jar --repo-mirror aliyun --use-http
[INFO] arthas-boot version: 3.3.9
[INFO] Process 14464 already using port 3658
[INFO] Process 14464 already using port 8563
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 14464
  [2]: 19092 org.jetbrains.jps.cmdline.Launcher
1
[INFO] arthas home: C:\Users\86156\.arthas\lib\3.4.0\arthas
[INFO] The target process already listen port 3658, skip attach.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'


wiki      https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version   3.4.0
pid       14464
time      2020-09-08 22:52:50

[arthas@14464]$

如果端口被占用了,也可以通过以下命令换成另一个端口号执行:

java -jar arthas-boot.jar --telnet-port 9922 --http-port -1

目录文件:下载的文件目录,可以从上面返回的信息获取,目录中的文件如下所示:

rm -rf ~/.arthas/
rm -rf ~/logs/arthas

二、IDE插件安装


再 File-> Setting-> Plugins 搜索 Alibaba Cloud Toolkit 安装并重启。插件安装的好处是省去了下载 jar包和配置环境的步骤:

插件会默认帮你下载arthas-boot.jar 并配置相关环境,然后在IDE的终端自动执行 java -jar arthas-boot.jar 命令。

确保你本地的项目启动成功,然后选择你项目pid的前缀,即pid前面的[]的标识,回车即可attach到你本地的 jvm进程。

出现 arthas的彩色字体即attach成功,下面就可以使用 arthas的命令解决具体的业务场景问题,命令的使用参照下面的说明。

这里arthas有个问题,比如使用watch命令查看代码里的值如果带有中文会出现乱码的问题,如果介意这一点可以先在IDE的终端退出arthas,使用exit,然后将上图第一个红圈里的命令增加-Dfile.encoding=UTF-8 编码,再启动 arthas即可解决中文乱码问题。即: java -Dfile.encoding=UTF-8 -jar D:\Users\yx_mao\.arthas\lib\3.2.0\arthas\arthas-boot.jar (后面的路径以你具体的目录为准)

另外如果觉得输入每次命令麻烦可以再下载一个arthas的辅助插件,这个插件可以自动生成对应的命令: Arthas Command

如果要远程诊断测试环境的代码,可以通过添加 host的方式连接,设置ip和用户名密码,但前提是你有测试环境的登录权限,当然插件也支持跳板机的方式,同样也会自动帮你在服务器上下载和安装arthas-boot.jar并执行。

线上环境慎重使用

三、常用命令接触


【dashboard 仪表板】

输入 dashboard(仪表板),会展示当前进程的信息,按 ctrl + c / q 可以中断执行。
【1】第一部分时显示 JVM中运行的所有线程:所在线程组,优先级,线程的状态,CPU的占有率,是否是后台进程等;
【2】第二部分显示的 JVM内存的使用情况;
【3】第三部分是操作系统的一些信息和 Java版本号;

【thread】

查看当前 jvm的线程堆栈信息。通过 thread 命令获取 arthas-demo 进程的主类。可以指定线程 ID,例如 thread tid参数说明:

参数名称参数说明
数字线程id
[n:]指定最忙的前 N个线程并打印堆栈
[b]找出当前阻塞其他线程的线程
[i <value>]指定 cpu占比统计的采样间隔
[arthas@20584]$ thread -n 2
"main" Id=1 cpuUsage=100% RUNNABLE
    at com.zzx.demo.demo.main(demo.java:11)


"Reference Handler" Id=2 cpuUsage=0% WAITING on java.lang.ref.Reference$Lock@4f2d17e4
    at java.lang.Object.wait(Native Method)
    -  waiting on java.lang.ref.Reference$Lock@4f2d17e4
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

【jad】

通过 jad来反编译 Main Class。例如:jad 包名.类名 或者只反编译某个方法:jad 包名.类名 方法名或者只查看某个类的源码,不看类加载器等:jad 包名.类名 --source-only

【watch】

监视:来查看函数的返回值。watch 包名.类名 方法 返回值(OGNL表达式)。作用:方法执行数据观测,让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL表达式进行对应变量的查看。watch 的参数比较多,主要是因为它能在 4个不同的场景观察对象。

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
express观察表达式
condition-express条件表达式
[b]在方法调用之前观察 before
[e]在方法异常时候观察 exception
[s]在方法返回之后观察 success
[f]在方法结束之后(正常返回和异常返回)观察 finish
[E]开启正则表达式匹配,默认为通配符匹配
[x:]指定输出结果的属性遍历深度,默认为1

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以可以这样写 "{params,returnObj}",只要是一个合法的 ognl表达式,都能被正常支持。
特别说明:【1】watch 命令定义了 4个观察事件点,即 -b方法调用前,-e 方法异常后, -s 方法返回后,-f 方法结束后;
【2】4个观察事件点 -b,-e,-s默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出;
【3】这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b事件点 params代表方法入参外,其余事件都代表方法出参;
【4】当使用 -b时,由于观察事件点是在方法调用前,此时返回值或异常均不存在;

举例一:观察 demo.MathGame类中 primeFactors 方法出参和返回值,结果属性遍历深度为2。列举了执行时间正则表达式的返回值

watch命令还可以根据耗时和具体的入参条件筛选过滤,只要符合Ognl语法,可以满足很多监控维度,如:  基于Ognl的一些特殊语法

例如观察参数里的订单号为 300的请求:

watch com.zzx.chinese.flight.business.dtomapper.xorderdetailsearch.XOrderDetailLoungeMapper toResponseType "params[0][0].airportLoungeProduct.{? #this.orderId=='300'}" -x 2

四、基础命令

Linux中的命令在 Arthas中可以使用;

【1】session:查看当前会话的信息。

1 # 还原指定类
2 reset Test
3 #还原所有 List结尾的类
4 reset *List
5 #还原所有类
6 reset

效果:

 class / classloader 相关命令:方便在运行的过程中来诊断代码,查看错误。
【1】jad 把字节码文件反编译成源代码,该功能基于一个第三方的反编译工具CFR实现;

#1、使用 jad 反编译类文件 [demo.MathGame] 输出到 /root/MathGame.java
jad --source-only demo.MathGame > /root/MathGame.java

注: 可以使用reset命令对增强后的class文件还原,或者shutdown服务端关闭时也会重置所有增强过的类。

【2】mc 在内存中把源代码编译成字节码文件;

# 2、上面的代码编辑完毕以后,使用 mc 对新的代码进行编译,并指定编译完之后的路径
[arthas@16866]$ mc /root/MathGame.java -d /root
Memory compiler output:
/root/demo/MathGame.class
Affect(row-cnt:1) cost in 377ms

【3】redefine 把新生成的字节码文件加载到内存中执行(加载外部的.class文件替换掉当前的类)类似于热加载或热修复的功能, 修改java文件编译后的, 将替换掉jvm已加载的.class类, 基于jdk本身的限制, 修改的class文件里不允许新增加成员变量和方法。

基于这个功能可以模拟一个简单的监控功能,比如在java文件的某个方法里加上调用耗时和请求参数的打印功能,然后使用redefine即可看到该方法的耗时时间和参数值, 并且不用重启服务;

#3、使用 redefine 命令加载新的字节码
redefine /root/demo/MathGame.class  # redefine 类的全路径

注意: 使用时需谨慎,确保修改的代码能正确编译并且逻辑正确,毕竟这个命令的杀伤力太大,验证过之后最好通过 reset 命令还原回来。

【4】sc:Search Class 查看运行中的类信息;
【5】sm:Search Method 查看运行中方法的信息;
【6】dump:将加载类的字节码文件保存到特定目录:logs/arthas/classdump/,不同的类加载器放在不同的目录下。

#把 String 类的字节码保存到 /logs/arthas/classdump
dump java.lang.String
#将 demo包下的所有类的字节码文件保存到 /logs/arthas/classdump/目录下
dump demo.*

【7】classloader:获取类加载器的信息。① classloader 命令将 JVM 中所有的 classloader的信息统计出来,并可以展示继承树,urls等。② 可以让指定的 classloader 去 getResources,打印出所有查找到的 resource 的url。对于 ResourceNotFoundException 异常比较有用。

参数名称参数说明
[I]按类加载实例进行统计
[t]打印所有 ClassLoader的继承树
[a]列出所有 ClassLoader加载的类,请谨慎使用
[c:]ClassLoader 的 hashcode
[c: r:]用 ClassLoader 去查找 resource
[c: load:]用 ClassLoader 去加载指定的类

五、monitor/trace相关


请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 shutdown 或将增强过的类执行 reset 命令。

【monitor】

monitor 命令:监控指定类中方法的执行情况。对匹配 class-pattern / method-pattern 的类,方法的调用进行监控。monitor 命令是一个非实时返回命令,实时返回命令是输入之后立即返回。而非实时返回命令,则是不断的等待目标 Java进行返回信息,直到用户输入 ctrl+c 为止。

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
[E]开启正则表达式匹配,默认为通配符匹配
[c:]统计周期,默认值为 120秒
# 每 5s 监控一次
monitor 包.类 方法名 -c 5

【trace】

对方法内部调用路径进行追踪,并输出方法路径上的每个节点上的耗时。trace 命令能主动搜索 class-pattern / method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。观察表达式的构成主要由 ognl表达式组成,所以你可以写成 “{params,returnObj}”,只要是一个合法的 ognl表达式,都能被正常支持。很多时候我们只想看到某个方法的 rt大于某个时间之后的 trace结果,现在 Arthas可以按照方法执行的耗时来进行过滤,例如:trace *StringUtils isBlank '#cost>100'表示当执行时间超过 100ms的时候,才会输出结果。 watch/stack/trace 这三个命令都支持 #cost 耗时条件过滤。也可以用正则表匹配路径上的多个类和函数,一定程度上达到多层 trace的效果:trace -E com.test.ClassA|org.test.ClassB method1|method2。

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法表达式匹配
condition-express条件表达式,使用OGNL表达式
[E]开启正则表达式,默认是通配符匹配
[n:]命令执行次数
#cost方法执行耗时,单位是毫秒

样例一:trace 函数指定类的指定方法;

如果输出的节点过多,也可以在命令后面加上筛选耗时('#cost > 100') 单位毫秒

【stack】

输出当前方法被调用的调用路径。很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从哪里被执行了,此时你就需要的是 stack命令。

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
condition-express条件表达式,OGNL
[E]开启正则表达式匹配,默认为通配符匹配
[n:]执行次数限制

样例一:查看方法的调用路径;

【tt】

time-tunnel 时间隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同时间下调用的信息进行观测。

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。这个时候要是能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。于是乎,TimeTunnel 命令就诞生了。

tt 的参数说明
-t记录某个方法在一个时间段中的调用。方法的每次执行情况。
-l显示所有已经记录的列表
-n 次数只记录多少次
-s 表达式搜索表达式
-i 索引号查看指定索引号的详细调用信息
-p重新调用指定的索引号时间碎片

这里着重说下 -n 参数, 当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。

此时你可以通过 -n 参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断tt命令的记录过程,避免人工操作无法停止的情况。

$ tt -t -n 15 com.zzx.Xproduct toResponseType

记录15次调用后结束,可以通过 tt -l 命令查看调用记录,也可以查看调用的详细信息 tt -i INDEX

$ tt -i 1002                                                                                                                                                                                                                                     
 INDEX          1002                                                                                                                                                                                                                             
 GMT-CREATE     2018-12-27 12:49:29                                                                                                                                                                                                              
 COST(ms)       173.237186                                                                                                                                                                                                                       
 OBJECT         0x42f6a6f9                                                                                                                                                                                                                       
 CLASS          com.ctrip.ibu.flight.business.dtomapper.xorderdetailsearch.XOrderDetailLoungeMapper                                                                                                                                              
 METHOD         toResponseType                                                                                                                                                                                                                   
 IS-RETURN      true                                                                                                                                                                                                                             
 IS-EXCEPTION   false                                                                                                                                                                                                                            
 PARAMETERS[0]  @ArrayList[                                                                                                                                                                                                                      
                    @XOrderInformationDTO[com.ctrip.ibu.flight.models.dto.xorderdetail.entity.XOrderInformationDTO@4b698c87],                                                                                                                    
                ]                                                                                                                                                                                                                                
 RETURN-OBJ     @ArrayList[                                                                                                                                                                                                                      
                    @XLoungeInfo[XLoungeInfo{segmentNo=1, loungeInfoList=[XLoungeDetail{loungeProductId=73986, loungeName=VIP Lounge, airport=Auckland International Airport, terminal=国内航站楼,
                    openTime=00:00-23:59,              salePrice=null,totalPrice=null, currency=null, loungePassengerInfoList=[LoungePassengerInfo{orderId=3046199910, productorderId=3046200017,
                    passengerProductId=3046200018, passengerName
                =LUCAS/SMITH, qrCode=, canRefund=null, productStatus=U, qrStatus=null, orderStatus=C}], boardingGate=null, supplierName=爱旅行, innerPhotos=[LoungePhoto{
                url=http://images4.c-ctrip.com/target/fd/flight/g4/M0A/23/92/CggYHFYbdSeAHw
                z_AAEOTpBr7pw943.png, description=休息区}, LoungePhoto{url=http://images4.c-ctrip.com/target/fd/flight/g3/M05/1D/18/CggYGVYbdTSAMfSPAAEDkA1N7V4323.png, description=休息区},
                LoungePhoto{url=http://images4.c-ctrip.com/target/fd/fligh
                t/g3/M06/1D/5D/CggYG1YbdUGAf0cCAAD5515lX0Y407.png, description=休息区}], segmentNo=1, sequence=1, expired=false, contactEmail=yx_mao@ctrip.com, cityCode=AKL, cityName=Auckland},
                XLoungeDetail{loungeProductId=5532, loungeName=VIP
                 Lounge, airport=Changi Airport, terminal=null, openTime=00:00-23:59, seviceFeatureList=[SeviceFeature{iconCode=e695, description=Baby free},
                 innerPhotos=[LoungePhoto{url=test/test4.png, description=测试描述4}, LoungePho
                to{url=test/test5.png, description=测试描述5}, LoungePhoto{url=test/test6.png, description=测试描述6}], segmentNo=1, sequence=2, expired=false, contactEmail=yx_mao@ctrip.com,
                cityCode=SIN, cityName=Singapore}]}],                     
                ]

重放功能: tt -i 1006 -p 因为tt 命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个 INDEX 编号的时间片自主发起一次调用。

当我们改了问题后,比如改了配置,需要在线上测试下是否修复的时候,可能会用到该功能,因为环境和数据的问题本地可能无法验证,但线上环境不可能让用户再调用一次,所以这个参数 -p 就可以再重新发起一次调用,但是是由阿尔萨斯内部发起的线程实现的,所以调用方不一样,而且如果之前的调用数据有从threaLocal里获取的话,这次调用代码里也无法获取,使用时需要注意。

其实最重要的还是要结合实际场景,因为线上真实环境去模拟用户再次发起调用如果牵涉到创单或支付流程的话还是要慎重的,否则可能引起一些非幂等的后果 : (

【profiler 火焰图】

profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。命令基本运行结构是 profiler 命令 [命令参数]  火焰图可以直观的分析代码占用cpu,内存,锁争用的情况,比较直观,但是这个命令只支持Linux和Mac系统,一般可以用于线上环境的诊断。

#启动 profiler
$ profiler start

 当使用profiler  stop 停止之后,就会给 arthas-output文件中生成一个火焰图,如下:通过 --format html可以指定输出的格式;

说明:纵轴表示栈帧深度,横轴表示代码占用CPU/内存的比例,点击可以查看详情。

profiler命令作用
profiler start启动 profiler,默认情况下,生产 cpu的火焰图
profiler list显示所有支持的事件
profiler getSamples获取已采集的 sample的数量
profiler status查看 profiler的状态,运行时间
profiler stop停止 profiler,生成火焰图的结果集,指定输出目录和输出格式:svg或html

全部命令: 官方手册 
注: 该命令也是通过多次采样的方式统计,会对系统性能有一些影响,使用慎重。

六、 jobs 后台异步任务命令


当线上出现偶发的问题,比如需要watch某个条件,而这个条件一天可能才会出现一次时,这种情况可以使用异步任务将命令在后台运行,而且可以保存到指定的文件, 方便查看。

比如:观察请求参数中订单号=300的操作, 并将调用记录保存到watch.log文件里,文件目录默认在当前用户 /Users/logs/arthas-cache/ 下

watch com.ctrip.ibu.flight.business.dtomapper.xorderdetailsearch.XOrderDetailLoungeMapper toResponseType "params[0][0].airportLoungeProduct.{? #this.orderId=='300'}" -x 2 >> watch.log &

或者记录某个耗时方法的调用记录
 

trace com.ctrip.ibu.flight.business.dtomapper.xorderdetailsearch.XOrderDetailLoungeMapper toResponseType >> trace.log &

具体操作可以查看官方使用手册:  异步调用

注意: 使用异步任务时,请勿同时开启过多的后台异步命令,以免对目标JVM性能造成影响。

七、实现原理


【1】JDK Instrumentation 和 Attach API 机制

sun.instrument.InstrumentationImpl 通过 instrument机制的实现可以构建一个独立于应用程序的代理程序Agent,再结合 attach机制来绑定我们的应用程序的pid就可以实现监控和协助运行在JVM上的程序,还可以替换和修改类的定义(主要通过redefine, addTransformer函数),比如实现虚拟机级别支持的AOP实现方式,attach机制可以提供一种 jvm进程间通信的能力,能让一个进程传命令给另外一个进程,并让它执行内部的一些操作,具体实现可以看下面的源码分析一节,instrument 和AttachAPI 是btrace,greys,arthas等监控工具的原理基础,如果要深入了解建议查看相关详细资料并自己做一个监控软件出来。

【2】ASM字节码增强技术

ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能,ASM可以从类文件中读入信息后,能够改变类行为,分析类信息,能够根据用户要求生成新类,
当然除了asm还有javassist字节码工具,虽然在反射性能上不如asm(但肯定优于jdk原生的反射),但提供了基于java语法实现操作字节码api,学习成本上比asm低。

【3】JVMTI

JVMTI(JVM Tool Interface)是Java虚拟机所提供的 native 编程接口,上面提到的instrument 底层就是基于此实现的,JVMTI 提供了可用于 debug 和 profiler 的接口,在 Java 5/6 中,虚拟机接口也增加了监听(Monitoring),线程分析(Thread analysis)以及覆盖率分析(Coverage Analysis)等功能。

正是由于 JVMTI 的强大功能,它是实现 Java 调试器,以及其它 Java 运行态测试与分析工具的基础,Instrumentation底层也是基于JVMTI实现的。

另外还有Eclipse,IntellJ Idea 等编译期的 debug功能都是基于JPDA(Java Platform Debugger Architecture)实现的,如下图:

Arthas正是使用Java6的Instrumentation特性,结合asm等第三方字节码操作框架的动态增强功能来实现的(核心功能实现在 com.taobao.arthas.core.advisor.Enhancer enhance() 方法中)

八、源码分析


源码部分目前只列出主要实现, 一些细节来不及看, 感兴趣的可以自己去git上下载下来看  GitHub - alibaba/arthas: Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas

根据官网入门手册里的 java -jar arthas-boot.jar 可知程序入口在这个jar包下,查看META-INF下的MANIFEST.MF文件可知(SPI机制)

这是java的一种机制,告知jdk jar包执行入口通过.MF,具体可参考 java.util.ServiceLoader 实现,感兴趣的也可以了解下 SPI 机制。

下面是引导程序Bootstrap的入口main方法,只列出主要代码逻辑,可对照源码查看,下面的所有代码分析中加注释"//"说明的都是关键地方

public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

    ...... 省略部分代码AnsiLog.info("Try to attach process " + pid);

    AnsiLog.debug("Start arthas-core.jar args: " + attachArgs);

    ProcessUtils.startArthasCore(pid, attachArgs); //加载arthas-agent.jar和arthas-core.jar, startArthasCore方法主要是利用了tool.jar这个包中的VirtualMachine.attach(pid)来实现

    AnsiLog.info("Attach process {} success.", new Object[]{pid});

    ......         

    Class<?> telnetConsoleClas = classLoader.loadClass("com.taobao.arthas.client.TelnetConsole"); //通过反射机制调用控制台命令行交互

    Method mainMethod = telnetConsoleClas.getMethod("main", String[].class); //TelnetConsole用到了JLine工具, JLine是一个用来处理控制台输入的Java类库,可以轻松实现Java命令行输入

}

通过上面的startArthasCore()方法内部ProcessBuilder类调用 arthas-core.jar 的进程服务, 下面就是arthas-core.jar包和入口执行类, 同样也可以通过查看MANIFEST.MF获得,下面的attachAgent方法正是使用了tool.jar这个包中的VirtualMachine.attach(pid)来实现,同时上面加载了自定义的agent代理,见下面 virtualMachine.loadAgent,这样就建立了连接。(如果感兴趣的话,可以去详细了解下attach底层技术JVMTI, 相关资料一节里也有)在运行前或者运行时,将自定义的 Agent加载并和 VM 进行通信。

Main-Class: com.taobao.arthas.core.Arthas

-------------------------------------------------------------------------- 

private void attachAgent(Configure configure) throws Exception {

    VirtualMachineDescriptor virtualMachineDescriptor = null;

    Iterator var3 = VirtualMachine.list().iterator();

    String targetJavaVersion;

    while(var3.hasNext()) {

        VirtualMachineDescriptor descriptor = (VirtualMachineDescriptor)var3.next();

        targetJavaVersion = descriptor.id();

        if (targetJavaVersion.equals(Integer.toString(configure.getJavaPid()))) {

            virtualMachineDescriptor = descriptor;

        }

    }

    VirtualMachine virtualMachine = null;

    try {

        if (null == virtualMachineDescriptor) {

            virtualMachine = VirtualMachine.attach("" + configure.getJavaPid()); //核心功能正是调用了com.sun.tools.attach.VirtualMachine类, 底层又调用了WindowsAttachProvider类, 这个类又是调用jdk的native方法实现的

        } else {

            virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);

        }

        Properties targetSystemProperties = virtualMachine.getSystemProperties();

        targetJavaVersion = targetSystemProperties.getProperty("java.specification.version");

        String currentJavaVersion = System.getProperty("java.specification.version");

        if (targetJavaVersion != null && currentJavaVersion != null && !targetJavaVersion.equals(currentJavaVersion)) {

            AnsiLog.warn("Current VM java version: {} do not match target VM java version: {}, attach may fail.", new Object[]{currentJavaVersion, targetJavaVersion});

            AnsiLog.warn("Target VM JAVA_HOME is {}, try to set the same JAVA_HOME.", new Object[]{targetSystemProperties.getProperty("java.home")});

        }

        virtualMachine.loadAgent(configure.getArthasAgent(), configure.getArthasCore() + ";" + configure.toString()); //这里通过loadAgent将我们自定义的Agent(arthas-core.jar)加载并和我们应用程序所在的JVM进行通信

    } finally {

        if (null != virtualMachine) {

            virtualMachine.detach();

        }

    }

}

然后是arthas-agent.jar代理包的MANIFEST.MF文件,该jar已经被第一步arthas-boot.jar里的ProcessUtils.startArthasCore方法加载。

Manifest-Version: 1.0

Premain-Class: com.taobao.arthas.agent.AgentBootstrap //jdk5的intrument机制,只能支持jvm启动前指定监控的类

Built-By: hengyunabc

Agent-Class: com.taobao.arthas.agent.AgentBootstrap //jdk6之后对intrument机制改进,可以在jvm启动后实时修改类,arthas的很多功能都是通过这个设置生效的

Can-Redefine-Classes: true //重新定义类, 正如上面介绍的redefine -p 指令一样, 通过这个属性设置告知jvm

Can-Retransform-Classes: true //转换类, watch, trace, monitor等命令都是动态修改类, 和Redefine-Classes的区别是直接在现有加载的class字节码基础上修改, 不需要一个新的class文件替换

Created-By: Apache Maven 3.5.3

Build-Jdk: 1.8.0_181

--------------------------------------------------------------------------

public static void premain(String args, Instrumentation inst) { //同上,main方法执行前,jdk5的intrument机制, 这里你已经拿到了Instrumentation对象实例

    main(args, inst);

}


public static void agentmain(String args, Instrumentation inst) { //main执行后, jdk6的intrument机制, 这里你已经拿到了Instrumentation对象实例

    main(args, inst);

}

private static synchronized void main(String args, final Instrumentation inst) {

    try {

        ps.println("Arthas server agent start...");

        int index = args.indexOf(59);

        String agentJar = args.substring(0, index);

        final String agentArgs = args.substring(index, args.length());

        File agentJarFile = new File(agentJar); //拿到arthas-agent.jar

        if (!agentJarFile.exists()) {

            ps.println("Agent jar file does not exist: " + agentJarFile);

        } else {

            File spyJarFile = new File(agentJarFile.getParentFile(), "arthas-spy.jar"); //拿到arthas-spy.jar, spy里面主要是些钩子类,基于aop有前置方法,后置方法,这样动态增强类,实现相应command功能

            if (!spyJarFile.exists()) {

                ps.println("Spy jar file does not exist: " + spyJarFile);

            } else {

                final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, agentJarFile); //类加载器加载agent和spy, 具体见下面的getClassLoader方法解析

                initSpy(agentLoader); //初始化钩子,这里面主要是通过反射的方式获取AdviceWeaver编织类, 比如前置方法,后置方法, 并配合asm实现类的动态增强

                Thread bindingThread = new Thread() {

                    public void run() {

                        try {

                            AgentBootstrap.bind(inst, agentLoader, agentArgs); //bind方法又通过反射调用了arthas-core.jar的ArthasBootstrap.bind方法, bind方法这里就不列出了, 可以自己看下

                        } catch (Throwable var2) {

                            var2.printStackTrace(AgentBootstrap.ps);

                        }

                    }

                };

                bindingThread.setName("arthas-binding-thread");

                bindingThread.start();

                bindingThread.join();

            }

        }

    } catch (Throwable var10) {

        var10.printStackTrace(ps);

        try {

            if (ps != System.err) {

                ps.close();

            }

        } catch (Throwable var9) {

            ;

        }

        throw new RuntimeException(var10);

    }

}


private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File agentJarFile) throws Throwable {

    inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile)); //这里把spy添加到jdk的启动类加载器里, 就是我们熟知的BootstrapClassLoader加载, 这样做的目的是为了下面的子加载器能共享spy, 我理解可能是很多命令都不是实时返回的,需要异步获取

    return loadOrDefineClassLoader(agentJarFile); //而agent是交给arthas自定义的classLoader加载的, 这样做的目的应该是不对我们的业务代码侵入

}

接下来就看core核心包里的AgentBootstrap.bind方法做了什么。

public void bind(Configure configure) throws Throwable {

    long start = System.currentTimeMillis();

    if (!this.isBindRef.compareAndSet(false, true)) {

        throw new IllegalStateException("already bind");

    } else {

        try {

            ShellServerOptions options = (new ShellServerOptions()).setInstrumentation(this.instrumentation).setPid(this.pid).setSessionTimeout(configure.getSessionTimeout() * 1000L);

            this.shellServer = new ShellServerImpl(options, this); //ShellServer服务初始化, 应该就是我们的命令行窗口服务

            BuiltinCommandPack builtinCommands = new BuiltinCommandPack(); //这一步就是初始化上面讲到各种命令的类, 比如"watch,trace,redefine...", 每个命令对应一个Command类,具体怎么实现可以看下一个源码分析

            List<CommandResolver> resolvers = new ArrayList();

            resolvers.add(builtinCommands);

            if (configure.getTelnetPort() > 0) {//注册telnet通信方式, 这个注册方法使用了一个第三方的termd工具,termd是一个命令行程序开发框架(termd内部又是基于netty实现的通信,可见netty的强大,韩国棒子思密达)

                this.shellServer.registerTermServer(new TelnetTermServer(configure.getIp(), configure.getTelnetPort(), options.getConnectionTimeout()));

            } else {

                logger.info("telnet port is {}, skip bind telnet server.", new Object[]{configure.getTelnetPort()});

            }

            if (configure.getHttpPort() > 0) {

                this.shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(), options.getConnectionTimeout())); //注册websocket通信方式

            } else {

                logger.info("http port is {}, skip bind http server.", new Object[]{configure.getHttpPort()});

            }

            Iterator var7 = resolvers.iterator();

            while(var7.hasNext()) {

                CommandResolver resolver = (CommandResolver)var7.next();

                this.shellServer.registerCommandResolver(resolver); //注册命令解析器

            }

            this.shellServer.listen(new BindHandler(this.isBindRef));

            logger.info("as-server listening on network={};telnet={};http={};timeout={};", configure.getIp(), new Object[]{configure.getTelnetPort(), configure.getHttpPort(), options.getConnectionTimeout()});

            UserStatUtil.arthasStart(); //这里就是启动命令行服务器,开始监听,到这步就可以接收客户端的命令输入了

            logger.info("as-server started in {} ms", new Object[]{System.currentTimeMillis() - start});

        } catch (Throwable var9) {

            logger.error((String)null, "Error during bind to port " + configure.getTelnetPort(), var9);

            if (this.shellServer != null) {

                this.shellServer.close();

            }

            throw var9;

        }

    }

}

剩下的就可以看下常用的命令是怎么实现逻辑了,比如 redefine,watch,jad 等,下面只列举了部分命令,感兴趣的可以看源码,大同小异。

RedefineCommand源码,对应"redefine"命令(每个命令都是继承AnnotatedCommand类,重写他的process方法实现)

public void process(CommandProcess process) {

    if (this.paths != null && !this.paths.isEmpty()) {

        ......省略部分代码

        Instrumentation inst = process.session().getInstrumentation(); //还是通过Instrumentation实现

        File file = new File(path); //path就是我们的redefine -p 后面指定的class文件路径, 然后下面还会校验文件是否存在

        f = new RandomAccessFile(path, "r"); //读取我们修改的class为byte[]字节数组

        ......省略部分代码

        Class[] var25 = inst.getAllLoadedClasses(); //通过Instrumentation获取jvm所有加载的类

            ......省略部分代码

            try {

                inst.redefineClasses((ClassDefinition[])definitions.toArray(new ClassDefinition[0])); //最终还是调用Instrumentation的redefineClasses方法实现的

                process.write("redefine success, size: " + definitions.size() + "\n");

            } catch (Exception var18) {

                process.write("redefine error! " + var18 + "\n");

            }

            process.end();

        }

    }

}

WatchCommand源码,对应"watch"指令(WatchCommand的实现是在EnhancerCommand里,因为这个指令和trace,stack,tt等都有相同的功能,所以放在父类里实现了)

public class Enhancer implements ClassFileTransformer {

    public static synchronized EnhancerAffect enhance(Instrumentation inst, int adviceId, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher, Matcher methodNameMatcher) throws UnmodifiableClassException {

        ......省略部分代码

        inst.addTransformer(enhancer, true); //将enhancer实例添加到转换器里,enhancer是ClassFileTransformer的实现类, ClassFileTransformer正是instrument的另一个关键组件,所有的转换实现都是基于ClassFileTransformer实现的

        if (GlobalOptions.isBatchReTransform) {

            ......省略部分代码

                while(var17.hasNext()) {

                    Class clazz = (Class)var17.next();


                    try {

                        inst.retransformClasses(new Class[]{clazz}); //重新转换指定的类,即动态修改原来的class文件,他和redefineClass方法的区别就是不需要源class文件,而是直接在现有的class文件上做修改,见下面的transform()方法

                        logger.info("Success to transform class: " + clazz);

                    } catch (Throwable var15) {

                        ......省略部分代码

                        throw new RuntimeException(var15);

                    }

                }

            }

        } finally {

            inst.removeTransformer(enhancer);

        }

        return affect;

    }

  

    public byte[] transform(final ClassLoader inClassLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

        // 这个方法正是重载了ClassFileTransformer.transform方法, 通过asm字节码工具的ClassReader和ClassWriter实现修改我们的class文件的

        // 代码这里就不展开了(其实我也看不懂... 内部都是些字节码语法,如果是用javassist还勉强能看)

    }

}

最后一个JadCommand命令实现比较简单,主要是通过一个第三方的反编译框架CFR实现的,cfr支持java8的一些新特性,比如lambda表达式的反编译,对新的jdk支持比较好。

private void processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst, Set<Class<?>> matchedClasses, Set<Class<?>> withInnerClasses) {

    ......省略部分代码

    try {

        ClassDumpTransformer transformer = new ClassDumpTransformer(allClasses);

        Enhancer.enhance(inst, transformer, allClasses);

        ......省略部分代码

        String source = Decompiler.decompile(classFile.getAbsolutePath(), this.methodName); //decompile()方法就是通过CFR实现的反编译

        ......省略部分代码

        process.write("");

        affect.rCnt(classFiles.keySet().size());

    } catch (Throwable var12) {

        logger.error((String)null, "jad: fail to decompile class: " + c.getName(), var12);

    }

}

总结:

通过上面的代码分析我们知道了JDK的这两项功能: VirtualMachine Instrumentation

Arthas的整体逻辑也是在jdk的Instrumentation基础上实现的,所有加载的类会通过Agent加载,addTransformer之后再进行增强,然后将对应的Advice织入进去,对于类的查找,方法的查找,都是通过SearchUtil来进行的,通过Instrument的loadAllClass方法将所有的JVM加载的class按名字进行匹配,再进行后续处理。

这些机制在以后的工作中如果遇到类似的问题也会给我们带来启发,嗯,Instrumentation是个好东西 : ) 

这个图是我基于目前的理解用visio画了大概流程:

以上只是主要流程和部分核心源码的讲解,鉴于本人能力有限,如果分析的不对还请指正,谢谢。

九. 注意事项


【1】Arthas对应用程序没有侵入(但对宿主机jvm有侵入),代码或项目中不需要引入jar包或依赖,因为是通过attach的机制实现的,我们的应用的程序和arthas都是独立的进程,arthas是通过和jvm底层交互来获取运行在其上的应用程序实时数据的,灵活查看运行时的值,这个和hickwall,jprofiler等监控软件的区别(JPofiler也有这样的功能,但是是收费的)动态增加aop代理和监控日志功能,无需重启服务,而且关闭arthas客户端后会还原所有增强过的类,原则上是不会影响现有业务逻辑的。

【2】对应用程序所在的服务器性能的影响,个别命令使用不当的话,可能会撑爆jvm内存或导致应用程序响应变慢,命令的输出太多,接口调用太频繁会记录过多的数据变量到内存里,比如tt指令,建议加 -n 参数 限制输出次数,sc * 通配符的使用不当,范围过大,使用异步任务时,请勿同时开启过多的后台异步命令,以免对目标JVM性能造成影响,一把双刃剑(它甚至可以修改jdk里的原生类),所以在线上运行肯定是需要权限和流程控制的。

十. 需要完善的点


【1】只有应用在线上业务的诊断上,才能体现它的价值,但是真正将这种类似的技术落地还是有很多事情要做的,阿里也只是开源了他的源码,并没有开源他的具体实践过程和心得,因为这个东西不可能让所有人都在线上搞的,肯定有一套严格的审核权限机制,以及配合这个工具使用的相关配套设施,比如一些命令的使用参数限制等等,而且测试和生产环境调试,需要OPS和架构组的支持,在可行性上还有很多事情要做,所以这里先给大家分享出来这个技术,扩展下知识面。

【2】二次封装,优化(增加默认指令输出的深度限制,json格式优化,前端交互界面,不需要手动输入类名方法名和ognl表达式等)。

java调试利器:arthas
infi
08-22 1586
目录 1、watch 2、getstatic 3、trace 4、ongl 相信很多做开发的同学都遇到过这样的问题:程序运行出错,根据返回的错误码定位到了代码中的某一行类、某一个方法。但是苦于没有日志,不知道方法运行的时候传进去的参数是什么,方法的返回值又是什么。而且,是因为在上线的环境中,不可能让你停机去加调试信息。如...
arthas_packaging最新.zip java调试工具
07-21
阿里 arthas全量jar包,官方3.1.0版本,java调试工具,监控java代码运行,调试代码,离线文档在另外一份资源里面,官网下载太慢,这里放一份,资源分不知道怎么修改,联系本人所有资源免费发送
arthasJava调试利器,线上Debug不是梦
最新发布
m0_59679869的博客
05-25 1050
Arthas(阿尔萨斯) 是阿里开源的一个Java在线分析诊断工具,相信会对大家有很大帮助
Arthas在线java进程诊断工具 在线调试神器
爱是与世界平行
11-05 2719
Arthas在线java进程诊断工具 在线调试神器 tag: java 诊断 堆栈 在线调试 耗时 死锁 arthas 阿里巴巴 Arthas 是 Alibaba 开源的Java诊断工具,深受开发者喜爱。 官网文档:https://arthas.aliyun.com/doc/ 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法在.
阿里云Arthas使用——在日志没有输出异常情况下,如何进行线上bug定位 &amp; stack命令 和 trace命令_arthas 阿里云
2401_84563179的博客
05-05 689
前端访问链接报错定位到调用的controller。
arthas排坑(一):远程监控
lang_programmer的博客
12-02 1万+
一、arthas简介 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;为了进一步提升自己的技术,最近有计划深入研究一些开源项目,于是在github上找到了arthas。它是阿里开源的Java诊断工具,据官方文档所述,它可以帮助你解决下列问题: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit...
阿里开源的 java 诊断工具—— Arthas
chuhui1765的博客
09-18 1498
Arthas 是 阿里巴巴最近开源出来的一个针对 java 的工具,主要是针对 java 的问题进行诊断! 一、概述 这个工具可以协助你做下面这些事情: 这个类是从哪个 jar 包加载而来的? 为什么会报各种类相关的 Exception? 线上遇到问...
Arthas 是Alibaba开源的Java诊断工具
10-09
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
Arthas开源的Java诊断工具-其他
06-11
bug当找不到java进程时,提示不清晰,应该提示用户执行jps命令增加一个 ognl 的在线,可编辑的示例,帮助用户自己调试 ognl表达式当Object输出时,可能会抛出异常,打印 ERROR DATA!!! exception message: null把 ...
使用Arthas抽丝剥茧排查线上应用日志打满问题
weixin_34232363的博客
10-18 1337
现象 在应用的 service_stdout.log里一直输出下面的日志,直接把磁盘打满了: 23:07:34.441 [TAIRCLIENT-1-thread-1] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 14 times in a...
KEIL在线调试(设置断点调试)
weixin_45675704的博客
08-20 3万+
KEIL在线调试 1 程序调试方式 (1) 使用SEGGER J-Flash(J-Link)下载程序到闪存中运行。 (2) 使用串口ISP 来下载HEX 文件到CPU 中运行。 (3) J-Link + MDK 组合,来在线调试程序(可下载、单步调试)。 下载:是将具体的文件如.axf或HEX下载到开发板的flash(SRAM)中去,在开发板启动之后就会运行所下载文件。根据运行结果再将修改的程序下至开发板中运行。 通过J-Link下载.axf文件时是在J-Link开发板与PC连接好后,通过点击KEIL中的“
Arthas-JAVA调试
A HUGE FATTY
08-17 1015
## 使用Arthas ``` curl https://alibaba.github.io/arthas/arthas-boot.jar -k -o /opt/arthas-boot.jar java -jar /opt/arthas-boot.jar java -jar /opt/arthas-boot.jar -h root@1d570207bde3:~# java -jar ar...
阿里重磅开源在线分析诊断工具Arthas(阿尔萨斯)
热门推荐
xiao_jun_0820的专栏
09-19 9万+
github地址: Arthas English version goes here. Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法...
Arthas 在线debug工具--使用记录
chunzhilianxue的博客
06-02 703
Arthas 阿里巴巴在线debug工具--使用记录Arthas官方介绍常用命令watch参数说明参考文档: Arthas官方介绍 Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法在线debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问
STM32的RAM下载在线调试设置
zhaohengnice的博客
07-10 3033
1.     64Kb = 64*1024Byte =0xf10000Byte2.     将64kB的RAM切割成两份,分别用来存放引导程序和引导程序起作用的缓存区域3.     在C/C++选项增加个向量VECT_TAB_RAM4.      修改base地址 5.     设置debug在线调试方式,注意下面有修改的参数6.     设置调试器,查看已经修改了7.     添加RAM.in...
在线调试环境
日々精進
11-08 259
网页开发的6种在线调试环境 http://jsfiddle.net/
使用IDEA调试Arthas
who7708的专栏
01-04 3360
使用IDEA调试Arthas准备工作准备调试使用的代码debug运行配置远程调试 arthas 准备工作 准备调试使用的代码 写一做供我们调试使用的代码. 或者直接使用arthas源码里的 demo.MathGame 也可以. debug运行 java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar arthas-demo.jar 配置远程调试 arthas 打开idea配置 注: 端口号与debug
工具 - 阿里 Java 在线调试工具 Arthas
大漠知秋的加油站
11-24 1343
Arthas
Arthas 线上环境调试 打印方法输入输出
木卯的专栏
03-30 1788
Arthas 线上环境调试 打印方法输入输出 Arthas curl -L https://arthas.aliyun.com/install.sh | sh vi as.sh 设置JAVA_HOME 查看进程: jps 启动: ./as.sh 进程号 watch -b com.tianan.v2x.axtagent.web.controller.api.MessagePushController rss ‘“params[0]=”+params[0]’ watch -b com.tianan.v2x.a
用 1000字描述开发工具Arthas 的使用技巧
09-06
Arthas 是一款面向 Java 应用的开发工具,旨在帮助开发人员更好地理解和调试 Java 应用程序的运行状态。它的功能非常丰富,可以帮助开发人员解决许多常见的问题,例如应用程序性能问题、内存泄漏、线程问题、类加载...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • ReentrantLock 锁详解 65191
  • 线程池的实现原理 12782
  • Nginx 代理 9764
  • Skywalking 链路追踪 8690
  • Java面试——数据库 8629

分类专栏

  • 面试 34篇
  • Java基础 25篇
  • 数据库 22篇
  • Java并发编程(多线程) 20篇
  • 分布式缓存【Redis】 16篇
  • 消息中间件Kafka/RabbitMQ/ActiveMQ 17篇
  • JVM Java 虚拟机 22篇
  • MyBatis & MyBatisPlus 4篇
  • 数据结构和算法 15篇
  • 设计模式 23篇
  • Netty 11篇
  • Spring 14篇
  • SpringBoot 20篇
  • 微服务【SpringCloud / Alibaba】 4篇
  • 注册中心ZK&Console&Eurka 6篇
  • Tomcat系列 1篇
  • 算法题 97篇
  • MongoDB 4篇
  • 项目管理 9篇
  • 分布式服务框架 3篇
  • 分布式事务 7篇
  • ClickHouse 1篇
  • 网络通信 1篇
  • 网站架构 & 性能调优 7篇
  • ShardingSphere & MyCat【分库分表】 4篇
  • 质量管理 2篇
  • 搜索引擎ES & Solr 1篇
  • Docker & K8S 5篇
  • 序列化 2篇
  • Nginx 5篇
  • SLB 1篇
  • 问题排查 46篇
  • RPC架构 2篇
  • 未来规划 1篇
  • Maven 2篇
  • Flink 23篇
  • 阿里云 9篇
  • 定时任务 1篇
  • Hadoop 8篇
  • HBase 1篇
  • Linux 7篇
  • Serverless
  • Cat
  • 前端 3篇
  • 开发工具 6篇
  • 应用工具类 1篇
  • 其他 11篇

最新评论

  • NameNode 和 SecondaryNameNode

    Srlua小谢: 这是一篇高质量的好文,深度理解和清晰的表达方式使复杂的技术概念变得容易理解,值得收藏点赞。博主用心很有耐心,更有对知识的热忱和热爱,写了这么实用有效的分享,期盼博主能够光顾我的博客,给予宝贵的指导!

  • NameNode 和 SecondaryNameNode

    24k纯甄: 支持支持支持支持支持

  • NameNode 和 SecondaryNameNode

    2301_79585944: 文章为我提供了宝贵的见解和学习资源,让我能够增长知识和技能,感谢分享!

  • YARN 的优化与实践

    waves浪游: YARN 的优化与实践

  • YARN 的优化与实践

    是阿建吖!: 博主的文章一直都是我的学习指南,内容详实,让我从中获益良多,每篇博文都是知识的瑰宝,我真的很喜欢你的风格,你的博客内容深入浅出,总是让我不再感到学习的困难,期待你的下一次精彩分享。

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • YARN 的优化与实践
  • 罗马数字转整数[简单]
  • YARN 组织架构
2024
06月 17篇
05月 35篇
04月 37篇
03月 38篇
02月 40篇
01月 57篇
2023年126篇
2022年19篇
2021年112篇
2020年18篇

目录

目录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿进阶

千言万语都不及一句“谢谢”

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司淮南SEO按天收费龙岗企业网站建设信阳设计网站报价哈密百度竞价包年推广伊犁外贸网站制作哪家好松岗网站设计哪家好成都网页设计推荐本溪外贸网站设计价格庆阳品牌网站设计报价兰州关键词按天扣费报价滨州百度网站优化价格咸阳网站制作马鞍山企业网站改版报价商洛网站改版价格临夏关键词按天计费价格永新百度竞价哪家好贵港品牌网站设计公司恩施如何制作网站哪家好永州百度标王哪家好天水seo网站优化价格福州推广网站公司临沂百度关键词包年推广推荐广州百搜标王报价大丰企业网站改版报价四平模板网站建设价格威海关键词排名公司北海关键词按天计费广东百度竞价哪家好沧州网站优化按天收费推荐大庆网站seo优化价格歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化