是不是在实际开发工作当中经常碰到自己写的代码在开发、测试环境行云流水稳得一笔,可一到线上就经常不是缺这个就是少那个反正就是一顿报错抽风似的,线上调试代码又很麻烦,让人头疼得抓狂;而且debug不一定是最高效的方法,遇到线上问题不能debug了怎么办。原先我们Java中我们常用分析问题一般是使用JDK自带或第三方的分析工具如jstat、jmap、jstack、 jconsole、visualvm、Java Mission Control、MAT等。但此刻的你没有看错,还有一款神器Arthas工具着实让人吃惊,可帮助程序员解决很多繁琐的问题,使得加班解决线上问题成为过去的可能性大大提高。
Arthas是一个Java诊断工具,由阿里巴巴中间件团队开源,目前已在Java开发人员中被广泛采用。Arthas能够分析,诊断,定位Java应用问题,例如:JVM信息,线程信息,搜索类中的方法,跟踪代码执行,观测方法的入参和返回参数等等。并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法的出入参,异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。简单的话:就是再不重启应用的情况下达到排查问题的目的。
在日常开发中,当我们发现应用的某个接口响应比较慢,这个时候想想要分析一下原因,找到代码中耗时的部分,比较容易想到的是在接口链路的 IO 操作上下游打印时间日志,再根据几个时间点的日志算出耗时长的 IO 操作。这种方式没有问题,但是加日志需要发布,既繁琐又低效,这个时候可以引入一些线上 debug 的工具,arthas 就是很好的一种,除了分析耗时,还可以打印调用栈、方法入参及返回,类加载情况,线程池状态,系统参数等等,其实现原理是解析 JVM 在操作系统中的文件,大部分操作是只读的,对服务进程没有侵入性,因此可以放心使用。
创建一个springboot项目并打包成arthas-demo-1.0.jar,启动arthas-demo-1.0.jar
代码示例如下
package cn.itxs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@SpringBootApplication
public class App
{
public static void main(String[] args) {
SpringApplication.run(App.class,args);
new Thread( () -> {
while (true) {
String str = UUID.randomUUID().toString().replaceAll("-", "");
}
},"cpu demo thread").start();
new Thread( () -> {
while (true) {
String str = UUID.randomUUID().toString().replaceAll("-", "");
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"cpu with sleep thread").start();
}
}
# 下载`arthas-boot.jar`这种也是官方推荐的方式
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 启动arthas-boot.jar,必须启动至少一个 java程序,否则会自动退出。运行此命令会自动发现 java进程,输入需要 attach 进程对应的序列号,例如,输入1按回车则会监听该进程。
java -jar arthas-boot.jar
# 比如输入JVM (jvm实时运行状态,内存使用情况等)

package cn.itxs.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/thread")
public class ThreadController {
private Object obj1 = new Object();
private Object obj2 = new Object();
@RequestMapping("/test")
@ResponseBody
public String test(){
new Thread(() -> {
synchronized (obj1){
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
synchronized (obj2){
System.out.printf("thread 1执行到此");
}
}
}
},"thread 1").start();
new Thread(() -> {
synchronized (obj2) {
synchronized (obj1){
System.out.printf("thread 2执行到此");
}
}
},"thread 2").start();
return "thread test";
}
}
SpringBoot启动类
package cn.itxs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App
{
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
# 访问页面http://192.168.50.100:8080/cpu/test
# 仪表盘命令,通过上面我们可以发现线程ID为29也即是线程名称为“cpu demo thread”占用的cpu较高
dashboard

# 当前最忙的前N个线程 thread -b, ##找出当前阻塞其他线程的线程 thread -n 5 -i 1000 #间隔一定时间后展示,本例中可以看到最忙CPU线程为id=45,代码行数为19
thread -n 5
# jad查看反编译的代码
jad cn.itxs.controller.CpuController


package cn.itxs.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/thread")
public class ThreadController {
private Object obj1 = new Object();
private Object obj2 = new Object();
@RequestMapping("/test")
@ResponseBody
public String test(){
new Thread(() -> {
synchronized (obj1){
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
}
synchronized (obj2){
System.out.println("thread 1执行到此");
}
}
},"thread 1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (obj2) {
synchronized (obj1){
System.out.println("thread 2执行到此");
}
}
},"thread 2").start();
return "thread test";
}
}
# 启动SpringBoot演示程序,访问页面http://192.168.50.100:8080/thread/test
# 运行arthas,查看线程
thread

# 查看阻塞线程
thread -b
# jad反编译查看代码
jad --source-only cn.itxs.controller.ThreadController


准备一个有问题的java类
package cn.itxs.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/hot")
public class HotController {
@RequestMapping("/test")
@ResponseBody
public String test(){
boolean flag = true;
if (flag) {
System.out.println("开始处理逻辑");
throw new RuntimeException("出异常了");
}
System.out.println("结束流程");
return "hot test";
}
}
启动Spring Boot程序,访问页面http://192.168.50.94:8080/hot/test发现报错,当我们分析到这段程序出异常后,我们分析异常后进行线上代码修改不停机不重新发包的情况下的热更新,操作流程如下:
# 第一步:`jad命令` 将需要更改的文件先进行反编译,保存下来 ,编译器修改,-c 指定这个类的classloader的哈希值,–source-only只显示源码,最后是文件反编译之后的存放路径
jad --source-only cn.itxs.controller.HotController > /home/commons/arthas/data/HotController.java
# 我们将HotController.java中的throw new RuntimeException("出异常了")代码删掉,修改完后需要将类重新加载到JVM
# 第二步:`SC命令` 查找当前类是哪个classLoader加载的,首先,使用sc命令找到要修改的类.sc全称-search class, -d表示detail,主要是为了获取classLoader的hash值
sc -d *HotController | grep classLoader
classLoaderHash 6267c3bb #类加载器 编号
# 第三步:`MC命令` 用指定的classloader重新将类在内存中编译
mc -c 6267c3bb /home/commons/arthas/data/HotController.java -d /home/commons/arthas/class
# 第四步:`redefine命令` 将编译后的类加载到JVM,参数是编译后的.class文件地址
redefine /home/commons/arthas/class/cn/itxs/controller/HotController.class
以上操作后再次访问一下页面http://192.168.50.94:8080/hot/test,发现异常没有了程序已经是我们修改正确后的,class文件替换成功,功能确实很强大。

上面我们是手工一步步执行,当然我们可以使用shell脚本串起来简单操作。
此外还可以安装Alibaba Cloud Toolkit热部署组件(一键retransform),热部署组件支持一键将编辑器中修改的 Java 源码快速编译,并更新到远端应用服务中,免去手动 dump、mc 的过程。此外,也可以一键还原 retransform 的类文件。

由于Arthas命令还是较复杂,Arthas-idea插件(部分命令可视化)是一个帮助生成命令的IDEA插件,使用文档:<u style="text-decoration: none; border-bottom: 1px solid rgb(68, 68, 68);">https://www.yuque.com/arthas-idea-plugin;</u>
安装基于Arthas实现的简单好用的热部署插件ArthasHotSwap可以一键生成热部署命令,提高我们线上维护的效率。

package cn.itxs.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/watch")
public class WatchController {
private static Random random = new Random();
private int illegalArgumentCount = 0;
@RequestMapping("/test")
@ResponseBody
public String test(){
String res = null;
try {
int number = random.nextInt() / 10000;
List<Integer> idStrs = this.getIdStr(number);
res = printList(number, idStrs);
}
catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
}
return res;
}
private List<Integer> getIdStr(int number) {
if (number < 5) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 5");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int count = 2;
while (count <= number) {
if (number % count == 0) {
result.add(count);
number /= count;
count = 2;
continue;
}
++count;
}
return result;
}
private String printList(int number, List<Integer> primeFactors) {
StringBuffer sb = new StringBuffer(number + "=");
for (int factor : primeFactors) {
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
return sb.toString();
}
}
启动Spring Boot程序,通过Jmeter每秒访问一次http://192.168.50.100:8080/watch/test

# Arthas中的**watch**命令可以让我们能方便的观察到指定方法的调用情况,可以观察到返回值,入参,以及变量等。
# watch 全路径类名 方法名 观察表达式 -x 3 ,观察表达式匹配ognl表达式,观察的维度也比较多。
# 比如:watch cn.itxs.controller.WatchController printList "{params,returnObj}" -x 3
# 查看printList方法的入参和出参,-x表示的是遍历结果深度默认1,只会打印对象的堆地址,看不到具体的属性值,-x 2会打印结果的属性值的信息 -x 3会输出入参属性值和结果属性值
# -n 1只抓紧一次,由于我们这里是模拟一直请求的
watch cn.itxs.controller.WatchController printList '{params}' -n 1
# -x 表示的是遍历结果深度默认3
watch cn.itxs.controller.WatchController printList '{params}' -n 1 -x 3
# params[0]代表第一个参数
watch cn.itxs.controller.WatchController printList '{params[0]}' -n 1 -x 3

# 方法的返回值
watch cn.itxs.controller.WatchController getIdStr '{returnObj}' -n 1 -x 3
# 方法参数和返回值
watch cn.itxs.controller.WatchController getIdStr '{params,returnObj}' -n 1 -x 3

# 观察方法执行前后当前对象属性值
watch cn.itxs.controller.WatchController getIdStr 'target.illegalArgumentCount'

# 观察异常信息,观察表达式里增加throwExp就好了。如果增加-e 参数就能过滤掉非异常的监听了。

# 在观察表达式后面,我们可以增加条件表达式,例如按时间过滤:#cost>0.5,单位是毫秒,那么控制台输出来的都是耗时在0.5毫秒以上的方法调用
watch cn.itxs.controller.WatchController getIdStr '{params}' '#cost>0.5'

# 按条件过滤观察params[1].size>4:这里支持ognl表达式。下面例子的意思是:第二个参数(也就是List primeFactors),的size大于4的时候才观察入参。watch cn.itxs.controller.WatchController printList '{params}' 'params[1].size>4' -x 3

monitor结果包括如下
# -c :统计周期,默认值为10秒
monitor -c 10 cn.itxs.controller.WatchController getIdStr
# 在方法调用之前计算condition-express,方法后可带表达式
monitor -b -c 10 cn.itxs.controller.WatchController getIdStr


# trace:方法内部调用路径,并输出方法路径上的每个节点上耗时
trace cn.itxs.controller.WatchController test -n 2
#包含jdk的函数--skipJDKMethod <value> skip jdk method trace, default value true.默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,需要显式设置--skipJDKMethod false。
trace --skipJDKMethod false cn.itxs.controller.WatchController test -n 2
# 调用耗时过滤,只会展示耗时大于10ms的调用路径,有助于在排查问题的时候,只关注异常情况
trace cn.itxs.controller.WatchController test '#cost > 1'

# 输出当前方法被调用的调用路径,getIdStr是从test方法调用进来的
stack cn.itxs.controller.WatchController getIdStr -n 1
# 输出当前方法被调用的调用路径,条件表达过滤,第0个参数小于0,也可以根据执行时间来过滤,'#cost>1'
stack cn.itxs.controller.WatchController getIdStr 'params[0]<0' -n 1


tt(TimeTunnel):方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。对于一个最基本的使用来说,就是记录下当前方法的每次调用环境现场。
# 记录指定方法的每次调用环境现场
tt -t cn.itxs.controller.WatchController getIdStr
# 列出所有调用记录
tt -l cn.itxs.controller.WatchController getIdStr

# 筛选调用记录
tt -s 'method.name=="getIdStr"'
# 查看调用信息
tt -i 1001

# 重新发起一次调用
tt -i 1001 -p

# 启动时指定Linux的ip
java -jar arthas-boot.jar --target-ip 192.168.50.94

profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。一般分析性能可以先通过Arthas profiler命令生成jfr文件;在本地通过jprofiler来分析jfr文件,定位谁在调用我。
# 启动profiler 默认情况下,生成的是cpu的火焰图,即event为cpu。可以用--event参数来指定
profiler start
# 获取已采集的sample的数量
profiler getSamples
# 查看profiler状态
profiler status
# 停止profiler 生成html格式结果,默认情况下,结果文件是html格式,也可以用--format参数指定;或者在--file参数里用文件名指名格式。比如--file /tmp/result.html
profiler stop --format html

通过浏览器查看arthas-output下面的profiler结果,<u style="text-decoration: none; border-bottom: 1px solid rgb(68, 68, 68);">http://192.168.50.100:3658/arthas-output/</u>
# profiler支持的events
profiler list

# 可以用--event参数指定要采样的事件,比如对alloc事件进入采样:
profiler start --event alloc
# 使用execute来执行复杂的命令
profiler execute 'start,framebuf=5000000'
# 生成 jfr格式结果;注意,jfr只支持在 start时配置。如果是在stop时指定,则不会生效。
profiler start --file /tmp/test.jfr
# 配置 include/exclude 来过滤数据
profiler start --include 'java/*' --include 'demo/*' --exclude '*Unsafe.park*'
# profiler执行 300 秒自动结束,可以用 -d/--duration 参数指定
profiler start --duration 300

http://ip:port/api,必须使用POST方式提交请求参数。如POST http://127.0.0.1:8563/api
{
"action": "exec",
"requestId": "req112",
"sessionId": "94766d3c-8b39-42d3-8596-98aee3ccbefb",
"consumerId": "955dbd1325334a84972b0f3ac19de4f7_2",
"command": "version",
"execTimeout": "10000"
}
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-agent-attach</artifactId>
<version>${arthas.version}</version>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-packaging</artifactId>
<version>${arthas.version}</version>
</dependency>
import com.taobao.arthas.agent.attach.ArthasAgent;
public class ArthasAttachExample {
public static void main(String[] args) {
ArthasAgent.attach();
}
}
我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我正在使用ruby1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www