背景
    JAVA中如何排查疑难杂症,如何动态获取应用信息,我们有BTrace!
PS:集团有大杀器arthas,这里我们先从最原始最广泛的BTrace开始,后面可以玩玩Greys(开源,强于BTrace)。
应用被tracle后,相关class会被改变,恢复需要重新编译,请自行把握。
一、下载安装BTtrace
wiki:https://github.com/jbachorik/btrace/wiki
BTrace博客:http://jbachorik.github.io/btrace/
JIRA(深入必看):https://kenai.com/jira/browse/BTRACE/?selectedTab=com.atlassian.jira.jira-projects-plugin:summary-panel
官网:https://kenai.com/projects/btrace/
github(1.3):https://github.com/jbachorik/btrace
IDE环境:引入BTrace相关Jar,在eclipse/IDEA编写;使用jvisualvm编写。
代码自动生成工具(使用必备):https://btrace.org/btrace/

下载最新版(1.3,JDK7以上):https://github.com/jbachorik/btrace/releases/tag/v1.3
BTrace Maven插件:https://github.com/btraceio/btrace-maven
BTrace1.2开发者说明:https://kenai.com/projects/btrace/pages/DeveloperGuide
BTrace1.2 API DOC:https://btrace.kenai.com/javadoc/1.2/index.html
配置环境变量:
BTRACE_HOME:~/user/btrace
PATH:$BTRACE_HOME/bin
JAVA_VERSION:MAC特有,详见btrace脚本内容,后文有说明。
Maven仓库(只有1.2.X版本,更高版本请自行编译发布到私服):

 <!-- 1.2版本-->

<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-agent</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-boot</artifactId>
<version>1.2.3</version>
</dependency>

注意:应用程序端不需要引入BTrace相关Jar,但为了编写脚本方便(IDE提示),可以以Provided引入它,不必Complie。

二、BTtrace常用脚本
解压btrace发现在%BTRACE_HOME%\bin有三个脚本:btrace、btracec、btracer,我们通过这三个脚本使用BTrace。
中文简述:
btrace:对运行中的JAVA程序执行btrace脚本。
btracec:编译btrace脚本。
btracer:让BTrace随JAVA应用启动加载。
btracer注意事项:
原理:java -javaagent:btrace-agent.jar=[<agent-arg>[,<agent-arg>]*]? <launch-args>
场景:When no custom agent arguments are required the btracer utility script may be used instead.
完整用法:https://github.com/jbachorik/btrace/wiki/btracer。
英文完整说明:
<btrace>/bin/btrace <PID> <trace_script>will attach to the java application with the given PID and compile and submit the trace script
<btrace>/bin/btracec <trace_script>will compile the provided trace script
<btrace>/bin/btracer <compiled_script><args to launch a java app> will start the specified java application with the btrace agent running and the script previously compiled by btracec loaded
详细看看btrace(1.3.4)脚本内容:

#! /bin/sh

if [ -z "$BTRACE_HOME" -o ! -d "$BTRACE_HOME" ] ; then
# resolve links - $0 could be a link to btrace's home
PRG="$0"
progname=`basename "$0"`
BTRACE_HOME=`dirname "$PRG"`/..
BTRACE_HOME=`cd "$BTRACE_HOME" && pwd`
fi
if [ -f "${BTRACE_HOME}/build/btrace-client.jar" ] ; then
if [ "${JAVA_HOME}" != "" ]; then
TOOLS_JAR="${JAVA_HOME}/lib/tools.jar"
if [ ! -f ${TOOLS_JAR} ] ; then
case "`uname`" in
Darwin*)
# In older JDK versions for Mac OS X, tools.jar is classes.jar
# and is kept in a different location. Check if we can locate
# classes.jar based on ${JAVA_VERSION}
TOOLS_JAR="/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Classes/classes.jar"
# if we can't find, try relative path from ${JAVA_HOME}. Usually,
# /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
# is JAVA_HOME. (or whatever version beyond 1.6.0!)
if [ ! -f ${TOOLS_JAR} ] ; then
TOOLS_JAR="${JAVA_HOME} /../Classes/classes.jar"
fi
# If we still can't find, tell the user to set JAVA_VERSION.
# This way, we can avoid zip file errors from the agent side
# and "connection refused" message from client.
if [ ! -f ${TOOLS_JAR} ] ; then
echo "Please set JAVA_VERSION to the target java version"
exit 1
fi
;;
esac
fi
${JAVA_HOME}/bin/java -cp ${BTRACE_HOME}/build/btrace-client.jar:${TOOLS_JAR}:/usr/share/lib/java/dtrace.jar com.sun.btrace.client.Main $*
else
echo "Please set JAVA_HOME before running this script"
exit 1
fi
else
echo "Please set BTRACE_HOME before running this script"
exit 1
fi

MAC 版本如果遇到错误,请留意%JAVA_VERSION%这个环境变量,原因见上面btrace脚本源码。

【${JAVA_HOME}/bin/java -cp ${BTRACE_HOME}/build/btrace-client.jar:${TOOLS_JAR}:/usr/share/lib/java/dtrace.jar com.sun.btrace.client.Main $*】btrace启动核心靠它!。
三、使用示例(监控HashMap扩容时的上下文情况)
简要说明:
table:哈希表数组;newCapacity:哈希表空间容量,即table数组长度;size:哈希表有效数据长度;threshold:有效数据个数阀值;

    void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {    //达到阀值 && 当前key对应的bucket被占用,触发扩容
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}


原始代码:

public class ExpandCapacity {
public static final int ONE_MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
mapExpandCapacity();
}
/**
* java.util.HashMap#resize(int)
* 何时扩容,发生扩容时候的上下文情况
*/
public static void mapExpandCapacity() throws Exception {
new Thread(new Runnable() {
public void run() {
while (true) {
//这是模拟主要逻辑
Map<Integer, Byte[]> map = new HashMap<Integer, Byte[]>();//16,0.75 16*0.75=12
int size = 100;
for (int i = 0; i < size; i++) {
map.put(i, new Byte[ONE_MB]);
}
System.out.println("Expand SIZE = " + map.size());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "mapExpandCapacity").start();
TimeUnit.HOURS.sleep(1);
}
}


trace脚本,特别注意OnMethod的中@Location#Where默认值是Before,代表方法被调用,但还未执行其内容:

@BTrace
public class ExpandCapacityBtrace {
@OnMethod(clazz = "java.util.HashMap", method = "resize",
location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/"))
public static void traceMapExpandCapacity(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod,
@Self Object self,int newCapacity) {
String point = Strings.strcat(Strings.strcat(probeClass, "."), probeMethod);//java/util/HashMap.resize
Class clazz = classForName("java.util.HashMap");
println(Strings.strcat(point, "======"));
//获取实例protected变量
Map.Entry[] table= (Map.Entry[]) get(field(clazz, "table", true), self);
int threshold = getInt(field(clazz, "threshold", true), self);
int size = getInt(field(clazz, "size", true), self);
println(Strings.strcat("newCapacity:", str(newCapacity)));
println(Strings.strcat("table.length:", str(table.length)));
println(Strings.strcat("size:", str(size)));
println(Strings.strcat("threshold:", str(threshold)));
println(Strings.strcat(point, "------------"));
}
}


输出(摘录核心,并进行格式化):

java/util/HashMap.resize======
newCapacity:32
table.length:16
size:16
threshold:12
java/util/HashMap.resize------------
java/util/HashMap.resizee======
newCapacity:64
table.length:32
size:32
threshold:24
java/util/HashMap.resize------------
java/util/HashMap.resizee======
newCapacity:128
table.length:64
size:64
threshold:48
java/util/HashMap.resize------------


初始(New HashMap()):newCapacity=16,size=0,table.length=16,threshold=12;
添加100个有效数据,会发生多次扩容,调用resize,首次调用:newCapacity=32,size=16,entry.length:16,threshold=12;
threshold每次执行完resize会改变为(int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1)。
四、常见问题
Q:btrace默认将信息打印到控制台,如何将信息打印到文件?
btrace pid btrace_scrpit.java > console.log
>代表覆盖,>>代表追加,其实就是把控制台的东东重定向到文件。
Q:典型错误NullPointerException:

......

Exception in thread "main" java.lang.NullPointerException
at com.sun.btrace.client.Client.submit(Client.java:361)
at com.sun.btrace.client.Main.main(Main.java:189)
DEBUG: sending exit command
Exception in thread "Thread-0" java.lang.IllegalStateException
at com.sun.btrace.client.Client.send(Client.java:466)
at com.sun.btrace.client.Client.sendExit(Client.java:400)
at com.sun.btrace.client.Main$2.run(Main.java:229)
at java.lang.Thread.run(Thread.java:745)

原因一:

应用程序端使用BTrace版本和客户端不一致。
为了方便BTrace脚本编写,IDE引入BTrace Jar,设置了Compile范围,应用启动加载时会加载相关class。
而运行btrace命令时,其BTrace版本和应用端的BTrace版本不一致,产生异常。
这里直接将IDE引入的BTrace设置成Provided。
原因二:
主要由于Btrace默认使用%JAVA_HOME%/lib/tools.jar,而应用程序类加载路径(-cp/-classpath)没有tools.jar,因此我们需要把tools.jar加到类路径下。
相关git issue:
https://github.com/jbachorik/btrace/issues/182
https://github.com/jbachorik/btrace/issues/169(解决方案)
https://github.com/jbachorik/btrace/issues/168
https://github.com/jbachorik/btrace/issues/182(解决方案)

五、BTrace详解
BTrace 的使用通过Annotations,详情:https://github.com/jbachorik/btrace/wiki/BTrace-Annotations
中文翻译(个人水平有限,建议中英对照):
  • 方法注解说明
@OnMethod:指定使用当前注解的方法应该在什么情况下触发,规范[@OnMethod(clazz=<cname_spec>[, method=<mname_spec>]? [, type=<signature>]? [, location=<location_spec>]?)]
    claszz属性,指定要匹配的类的全限定类名,可以用正则表达式:"/类名的Pattern/"匹配,用”+类名”匹配所有子类,用”@某某注解”匹配用该注解注解过的类。
    method属性,指定要匹配的方法名称,可以用正则表达式:/方法名称的Pattern/匹配。
    type属性,类似JAVA方法声明,但不包括方法名,参数名,异常。如void(java.lang.String)可以用于匹配:public void funcName(String param) throws Exception。
    location属性,可简单认为指明该BTrace方法在何时执行(如THROW/RETURN/ENTRY,详情参见@Location) 。
@OnTimer:指定一个定时任务。
@OnExit:当脚本运行Sys.exit(code)时触发。
@OnError:当脚本运行抛出异常时触发。
@OnEvent:脚本运行时Ctrl+C可以发送事件。
@OnLowMemory:让你指定一个阀值,内存低于阀值触发。
@Location注意点:
Kind.ENTRY意指进入匹配probe点,与@Location设置的clazz和method没有任何关系。
Kind.CALL意指从某个匹配probe的方法中调用了匹配A class method的点,一定要和clazz,method配合使用。clazz和method的默认值为"",所以不能被匹配。
 Kind.ENTRY只关注调用X方法(OnMethod的clazz,method),Kind.CALL还关注X方法中,还调用了哪些类哪些方法(Location的class,method)。
  • 方法参数注解说明
@Self:用来指定被trace方法的this,可参考例子AWTEventTracer.java 和 AllCalls1.java。
@ProbeClassName:目标类名。
@ProbeMethodName:目标方法名。
@TargetInstance:配合@Location#Kind.CALL使用,匹配@Location#clazz的Class实例。
@TargetMethodOrField:配合@Location#Kind.CALL使用,匹配@Location#method的MethodOrField值。
@Retrun:配合@Location#Kind.Return使用,用来指定被trace方法的返回对象,可参考例子Classload.java。
@Duration:配合@Location#Kind.Return/ Kind.ERROR使用,目标方法执行时间,单位是纳秒。
如何获取方法参数?可参见@Kind#CALL说明。
如方法java.util.HashMap#resize(int newCapacity),要获取newCapacity,则可以在trace方法当参数注入,如下:

public static void traceMapExpandCapacity(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod, @Self Object self,int newCapacity)

其中newCapacity则是resize调用时的newCapacity参数。
@TargetInstance 和 @TargetMethodOrField的理解

如果是probe A class方法调用匹配了B class,@TargetInstance 返回的就是B class,@Self返回的就是A class。
@Retrun:用来指定被trace方法的返回对象,可参考例子Classload.java。
Trace Scripts 说明,主要是一些约束,描述了能做什么,不能做什么:https://github.com/jbachorik/btrace/wiki/Trace-Scripts
samples目录有很多样例,想写又不会写时,可以进去看看,这有中文说明:http://mgoann.iteye.com/blog/1409667

六、参考文献
1. btrace记忆:http://agapple.iteye.com/blog/962119
2. btrace一些你不知道的事(源码入手),这篇很有料,请看:http://agapple.iteye.com/blog/1005918
附:思维导图(转载)

Btrace详细指南(JDK7,监控HashMap扩容)相关推荐

  1. hashmap扩容 面试_HashMap面试,看完这一篇就够了(上)

    以下HashMap源码的解析都是基于java8来讲解的. HashMap的结构是数组加链表的形式(jdk7中也是),在java8中引入了红黑树,由于红黑树的时间复杂度是O(log n),引入红黑树是为 ...

  2. hashmap扩容机制_图文并茂:HashMap经典详解!

    点击上方 Java后端,选择 设为星标 优质文章,及时送达 代码中的注解多看几遍,其中HashMap的扩容机制是要必懂知识!结合图片一起理解! 什么是 HashMap? HashMap 是基于哈希表的 ...

  3. hashmap扩容线程安全问题_HashMap线程不安全的体现

    1.多线程put操作后,get操作导致死循环. 2.多线程put非NULL元素后,get操作得到NULL值. 3.多线程put操作,导致元素丢失. 比如一个 ArrayList 类,在添加一个元素的时 ...

  4. hashmap扩容机制_图文并茂,HashMap经典详解!

    Java面试笔试面经.Java技术每天学习一点 公众号Java面试 关注我不迷路 作者:feigeswjtu 来源:https://github.com/feigeswjtu/java-basics ...

  5. 七、JDK1.7中HashMap扩容机制

    导读 前面文章一.深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二.Jdk1.7和1.8中HashMap数据结构及源码分析 .三.JDK1.7和1.8Hash ...

  6. 八、JDK1.8中HashMap扩容机制

    导读 前面文章一.深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二.Jdk1.7和1.8中HashMap数据结构及源码分析 .三.JDK1.7和1.8Hash ...

  7. React Native布局详细指南

    本文出自<React Native学习笔记>系列文章. 一款好的APP离不了一个漂亮的布局,本文章将向大家分享React Native中的布局方式FlexBox.  在React Nati ...

  8. hashmap扩容线程安全问题_HashMap在1.7 1.8中的线程安全问题

    HashMap的线程不安全主要体现在下面两个方面: 在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况. 在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况. ? ? 常被问 ...

  9. hashmap扩容_面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

最新文章

  1. Javascript中的自执行匿名函数
  2. Matlab稀疏矩阵
  3. Linux 内核101:[译]并发导论
  4. Jmeter Web 性能测试入门 (六):Jmeter 解析 response 并传递 value
  5. 把linux插足到域
  6. C#中的Socket编程-TCP客户端
  7. RabbitMQ控制台详解
  8. uniapp+typeScript+vue3.0+vite
  9. android 接收SDCcard插拔的广播
  10. 【iOS】Touch Drag Inside 和 Touch Drag Outside、Touch Drag Enter、Touch Drag Exit的区别
  11. 无线传感网1-简单介绍
  12. 工商管理专业知识与实务(中级)【4】
  13. 原生AJAX GET请求
  14. 微信小程序测试二维码跳转链接
  15. LVM -逻辑卷管理
  16. Linux重置root 密码
  17. itextpdf将带复选框的html_使用flying-saucer 实现 html转pdf实现input框select,textarea checkbox等的显示...
  18. CSS 如何做一个比较真实有感觉的阴影效果
  19. CSS -- CSS字体样式、文本样式、去掉列表的小圆点、背景、背景渐变
  20. 在Windows环境下编译VPX

热门文章

  1. MySQL高级-索引是什么
  2. 需求用例分析之二:级别设置
  3. (time.h) 自己用
  4. 创智播客 大数据_中国电信启动嘉创智谷——新仓远景产业园5G部署
  5. java 找不到mysql驱动_java lib目录添加了mysql驱动包,仍然找不到class??
  6. 直播预告丨企服企业如何科学搭建规模化获客体系?
  7. Unity-2017.2官方实例教程Roll-a-ball(一)
  8. python excel 操作
  9. android(cm11)状态栏源码分析(一)
  10. 再见,2014;您好,2015!