├── tools ├── jvm.lib │ ├── jtop.jar │ ├── MonBuffers.class │ ├── jvm.list │ ├── jvm.dsc │ └── MonBuffers.java ├── housemd.lib │ ├── housemd_2.9.2-0.2.6.min.jar │ └── README.md ├── jarfind ├── jargrep ├── housemd ├── findcycle ├── jarconfict └── jvm ├── .gitignore └── README.md /tools/jvm.lib/jtop.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdaxb/wtool_java/HEAD/tools/jvm.lib/jtop.jar -------------------------------------------------------------------------------- /tools/jvm.lib/MonBuffers.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdaxb/wtool_java/HEAD/tools/jvm.lib/MonBuffers.class -------------------------------------------------------------------------------- /tools/housemd.lib/housemd_2.9.2-0.2.6.min.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdaxb/wtool_java/HEAD/tools/housemd.lib/housemd_2.9.2-0.2.6.min.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # maven 2 | target/ 3 | 4 | # eclipse ignore 5 | .settings/ 6 | .project 7 | .classpath 8 | bin/ 9 | 10 | # idea ignore 11 | .idea/ 12 | *.ipr 13 | *.iml 14 | *.iws 15 | 16 | # temp ignore 17 | *.log 18 | *.cache 19 | *.diff 20 | *.patch 21 | *.tmp 22 | *.swp 23 | *.swo 24 | 25 | # system ignore 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # C/C++ 30 | *.o 31 | .dailycheck 32 | -------------------------------------------------------------------------------- /tools/jarfind: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### find file in jars 3 | 4 | if [ $# -lt 2 ];then 5 | echo "Usage : wtool jarfind file path"; 6 | exit 1; 7 | fi 8 | 9 | findinjar(){ 10 | result=`unzip -l "$1"|grep "$2"` 11 | if [ $? = 0 ];then 12 | echo "$1 :" 13 | echo "$result" 14 | fi 15 | } 16 | keyword=$1; 17 | while true; do 18 | folder=$2 19 | if [ "$folder" = "" ];then 20 | break; 21 | fi 22 | if [ -d "$folder" ]; then 23 | echo "begin searching file $keyword in $folder" 24 | for filename in `find $folder -name '*.jar'`; do 25 | findinjar "$filename" "$keyword" 26 | done 27 | elif [ -f "$folder" ]; then 28 | findinjar "$folder" "$keyword" 29 | fi 30 | shift; 31 | done 32 | -------------------------------------------------------------------------------- /tools/jargrep: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### grep text in jars 3 | 4 | if [ $# -lt 2 ];then 5 | echo 'Usage : wtool jargrep text ' 6 | exit 1; 7 | fi 8 | 9 | LOOK_FOR=$1 10 | LOOK_FOR=`echo ${LOOK_FOR//\./\/}` 11 | while true; do 12 | folder=$2 13 | if [ "$folder" = "" ];then 14 | break; 15 | fi 16 | echo "finding '$LOOK_FOR' in $folder ..." 17 | if [ -d "$folder" ]; then 18 | for i in `find $2 -name "*jar"` 19 | do 20 | unzip -p $i | grep "$LOOK_FOR" > /dev/null 21 | if [ $? = 0 ] 22 | then 23 | echo "==> Found \"$LOOK_FOR\" in $i" 24 | fi 25 | done 26 | fi 27 | if [ -f "$folder" ]; then 28 | zipgrep "$LOOK_FOR" "$folder" 29 | fi 30 | shift; 31 | done; 32 | -------------------------------------------------------------------------------- /tools/housemd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### start housemd debugger 3 | 4 | base_dir=$(dirname "$(echo "$0" | sed -e '')") 5 | if [ $# -eq 0 ];then 6 | echo "Usage : wtool housemd pid [java_home]" 7 | echo "More details about housemd please read doc: $base_dir/housemd.lib/README.md" 8 | echo "Site: https://github.com/CSUG/HouseMD/wiki/UserGuideCN" 9 | exit 1; 10 | fi 11 | if [ ! "$JAVA_HOME" = "" ] ;then 12 | java_home=$JAVA_HOME 13 | fi 14 | if [ $# -gt 1 ];then 15 | java_home=$2; 16 | fi 17 | if [ ! -f "$java_home/lib/tools.jar" ] && [ "$(uname)" != "Darwin" ];then 18 | echo "$java_home/lib/tools.jar not found!" 19 | exit 1; 20 | fi 21 | 22 | java -Xbootclasspath/a:$java_home/lib/tools.jar -jar $base_dir/housemd.lib/housemd_2.9.2-0.2.6.min.jar $1 23 | -------------------------------------------------------------------------------- /tools/findcycle: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### find cycle in maven depnedency tree 3 | 4 | if [ $# -gt 0 ];then 5 | sourcepath=$1 6 | else 7 | sourcepath=`pwd` 8 | fi 9 | if [ ! -f "$sourcepath/pom.xml" ]; then 10 | echo "$sourcepath is not a vaild maven project!" 11 | echo 'Usage : wtool findcycle [path]' 12 | exit 1; 13 | fi 14 | mvn=`which mvn` 15 | if [ "$mvn" = "" ];then 16 | echo "counld not found mvn in PATH,exit!" 17 | exit 1; 18 | fi 19 | 20 | cd $sourcepath 21 | echo "scan cycle dependency in $sourcepath ..." 22 | mvn dependency:tree -Dverbose | awk -F'- ' '{if(index($2,"maven-dependency-plugin")>0){indent=0;}else{indent=length($1);}stack[indent]=$2;if(index($0,"for cycle")>0){print "****found cycle****";for(i=0;i<=indent;i++){if(stack[i]!=null){print "->"stack[i]}}}}' 23 | echo "scan finished!" 24 | -------------------------------------------------------------------------------- /tools/jvm.lib/jvm.list: -------------------------------------------------------------------------------- 1 | ========线程相关======= 2 | s;java -jar ${WTOOL_JVM_LIB}/jtop.jar -size H -thread 10 -stack 100 [arg] pid;查看占用cpu最高的线程情况;0,3;; 3 | b;jstack pid;打印所有线程;0;; 4 | a;echo -n "Thread count:"&&jstack pid |grep prio= |grep tid= |grep nid=|wc -l;打印线程数;0;; 5 | c;jstack pid |grep 'java.lang.Thread.State:'|sort|uniq -c;按线程状态统计线程数;0;; 6 | ========GC相关======= 7 | h;jstat -gccause pid [arg];垃圾收集统计(包含原因);0,3;可以指定间隔时间及执行次数,默认10秒;1000 10 8 | f;jstat -gccapacity pid [arg];显示堆中各代的空间;0,3;1000 5 9 | d;jstat -gcutil pid [arg];垃圾收集统计。;0,3;可以指定间隔时间及执行次数,默认10秒;1000 10 10 | l;jstat -gcpermcapacity pid [arg];打印perm区内存情况*会使程序暂停响应*;0; 11 | r;java -cp ${JAVA_HOME}/lib/tools.jar:${WTOOL_JVM_LIB} MonBuffers pid [arg] ;查看directbuffer情况;0,3;;1000 5 12 | ========堆对象相关======= 13 | m;jmap -dump:file=[arg] pid;dump heap到文件*会使程序暂停响应*;0; 默认保存到`pwd`/dump.bin,可指定其它路径;`pwd`/dump.bin 14 | e;jmap -histo:live pid [arg] >/dev/null;触发full gc。*会使程序暂停响应*;0; 15 | k;jmap -heap pid [arg];打印jvm heap统计*会使程序暂停响应*;0; 16 | k;jmap -histo pid |sort -n -r -k$(([arg]+1)) |head -n 21;打印jvm heap中top20的对象。*会使程序暂停响应*;0;参数:1:按实例数量排序,2:按内存占用排序,默认为1;1 17 | q;jmap -histo:live pid |sort -n -r -k$(([arg]+1)) |head -n 21;触发full gc后打印jvm heap中top20的对象。*会使程序暂停响应*;0;参数:1:按实例数量排序,2:按内存占用排序,默认为1;1 18 | p;jmap -permstat pid [arg];输出所有类装载器在perm里产生的对象。;0;可以指定间隔时间及执行次数 19 | ========其它======= 20 | g;jmap -finalizerinfo pid [arg];打印finalzer队列情况;0; 21 | j;jstat -class pid [arg];显示classloader统计;0,3; 22 | i;jstat -compiler pid [arg];显示jit编译统计;0,3; 23 | n;jstack -m pid ;死锁检测;0;; 24 | o;sleep [arg];等待X秒,默认为1;1,1;;1 25 | -------------------------------------------------------------------------------- /tools/jarconfict: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### detect jar conficts 3 | #source from http://hongjiang.info/duplicate-classes-check-script/ 4 | if [ $# -eq 0 ];then 5 | echo "Usage: wtool jarconfict path" 6 | exit -1 7 | fi 8 | 9 | if [ ! -d "$1" ]; then 10 | echo "not a directory" 11 | exit -2 12 | fi 13 | 14 | tmpfile="/tmp/.cp$(date +%s)" 15 | tmphash="/tmp/.hash$(date +%s)" 16 | verbose="/tmp/cp-verbose.log" 17 | 18 | declare -a files=(`find "$1" -name "*.jar"`) 19 | for ((i=0; i < ${#files[@]}; i++)); do 20 | jarName=`basename ${files[$i]}` 21 | list=`unzip -l ${files[$i]} | awk -v fn=$jarName '/\.class$/{print $NF,fn}'` 22 | size=`echo "$list" | wc -l` 23 | echo $jarName $size >> $tmphash 24 | echo "$list" 25 | done | sort | awk 'NF{ a[$1]++;m[$1]=m[$1]","$2 }END{for(i in a) if(a[i] > 1) print i,substr(m[i],2) }' > $tmpfile 26 | 27 | awk '{print $2}' $tmpfile | 28 | awk -F',' '{i=1;for(;i<=NF;i++) for(j=i+1;j<=NF;j++) print $i,$j}' | 29 | sort | uniq -c | sort -nrk1 | while read line; do 30 | dup=${line%% *} 31 | jars=${line#* } 32 | jar1=${jars% *} 33 | jar2=${jars#* } 34 | len_jar1=`grep -F "$jar1" $tmphash | grep ^"$jar1" | awk '{print $2}'` 35 | len_jar2=`grep -F "$jar2" $tmphash | grep ^"$jar2" | awk '{print $2}'` 36 | len=$(($len_jar1 > $len_jar2 ? $len_jar1 : $len_jar2)) 37 | per=$(echo "scale=2; $dup/$len" | bc -l) 38 | echo ${per/./} $dup $jar1 $jar2 39 | done | sort -nr -k1 -k2 | 40 | awk 'NR==1{print "Similarity DuplicateClasses File1 File2"}{print "%"$0}'| column -t 41 | 42 | sort $tmpfile | awk '{print $1,"\n\t\t",$2}' > $verbose 43 | echo "See $verbose for more details." 44 | 45 | rm -f $tmpfile 46 | rm -f $tmphash 47 | -------------------------------------------------------------------------------- /tools/jvm.lib/jvm.dsc: -------------------------------------------------------------------------------- 1 | j:Column | Description 2 | j:Loaded | 被读入类的数量 3 | j:Bytes | 被读入的字节数(K) 4 | j:Unloaded | 被卸载类的数量 5 | j:Bytes | 被卸载的字节数(K) 6 | j:Time | 花费在load和unload类的时间 7 | 8 | i:Column | Description 9 | i:Compiled | 被执行的编译任务的数量 10 | i:Failed | 失败的编译任务的数量 11 | i:Invalid | 无效的编译任务的数量 12 | i:Time | 花费在执行编译任务的时间. 13 | i:FailedType | 最近失败编译的编译类弄. 14 | i:FailedMethod | 最近失败编译的类名和方法名 15 | 16 | f:Column | Description 17 | f:NGCMN | 年轻代的最小容量 (KB). 18 | f:NGCMX | 年轻代的最大容量 (KB). 19 | f:NGC | 当前年轻代的容量 (KB). 20 | f:S0C | 当前S0的空间 (KB). 21 | f:S1C | 当前S1的空间 (KB). 22 | f:EC | 当前eden的空间 (KB). 23 | f:OGCMN | 年老代的最小容量 (KB). 24 | f:OGCMX | 年老代的最大容量 (KB). 25 | f:OGC | 当前年老代的容量 (KB). 26 | f:OC | 当前年老代的空间 (KB). 27 | f:PGCMN | 永久代的最小容量 (KB). 28 | f:PGCMX | 永久代的最大容量 (KB). 29 | f:PGC | 当前永久代的容量 (KB). 30 | f:PC | 当前永久代的空间 (KB). 31 | f:YGC | 年轻代gc的次数 32 | f:FGC | full gc的次数 33 | 34 | d:Column | Description 35 | d:S0 | S0使用百分比 36 | d:S1 | S1使用百分比 37 | d:E | eden使用百分比 38 | d:O | old使用百分比 39 | d:P | perm使用百分比 40 | d:YGC | 年轻代gc次数 41 | d:YGCT | 年轻代gc时间 42 | d:FGC | full gc次数 43 | d:FGCT | full gc时间 44 | d:GCT | 垃圾收集总时间 45 | 46 | h:Column | Description 47 | h:S0 | S0使用百分比 48 | h:S1 | S1使用百分比 49 | h:E | eden使用百分比 50 | h:O | old使用百分比 51 | h:P | perm使用百分比 52 | h:YGC | 年轻代gc次数 53 | h:YGCT | 年轻代gc时间 54 | h:FGC | full gc次数 55 | h:FGCT | full gc时间 56 | h:GCT | 垃圾收集总时间 57 | h:LGCC | 最近垃圾回收的原因. 58 | h:GCC | 当前垃圾回收的原因. 59 | 60 | p:输出所有类装载器在堆里产生的对象 包括每个装载器的名字,活跃,地址,父装载器,和其总共加载的类大小 61 | 62 | 63 | l:Column | Description 64 | l:PGCMN | 永久代最小容量 (KB). 65 | l:PGCMX | 永久代最大容量 (KB). 66 | l:PGC | 当前永久代的容量 (KB). 67 | l:PC | 当前永久代的空间 (KB). 68 | l:YGC | 年轻代gc次数 69 | l:FGC | full gc次数 70 | l:FGCT | full gc时间 71 | l:GCT | 垃圾收集总时间 72 | 73 | k:class name对应的就是Class文件里的class的标识 74 | k:B代表byte 75 | k:C代表char 76 | k:D代表double 77 | k:F代表float 78 | k:I代表int 79 | k:J代表long 80 | k:Z代表boolean 81 | k:前边有[代表数组 82 | k:对象用[L+类名表示 83 | 84 | q:class name对应的就是Class文件里的class的标识 85 | q:B代表byte 86 | q:C代表char 87 | q:D代表double 88 | q:F代表float 89 | q:I代表int 90 | q:J代表long 91 | q:Z代表boolean 92 | q:前边有[代表数组 93 | q:对象用[L+类名表示 94 | 95 | r:应用程序的directbuffer使用情况(单位为byte) 96 | r:direct代表ByteBuffer.allocateDirect分配的nio directbuffer 97 | r:mapped代表FileChannel.map分配的mapped memory 98 | 99 | s:查看占用cpu最高的线程情况 100 | s:使用了开源项目jtop,项目地址:https://bitbucket.org/hatterjiang/jtop,作者:hatterjiang 101 | -------------------------------------------------------------------------------- /tools/jvm.lib/MonBuffers.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.util.*; 3 | import java.lang.management.BufferPoolMXBean; 4 | import java.lang.management.ManagementFactory; 5 | import javax.management.MBeanServerConnection; 6 | import javax.management.ObjectName; 7 | import javax.management.remote.*; 8 | 9 | import com.sun.tools.attach.VirtualMachine; // Attach API 10 | 11 | /** 12 | * Simple tool to attach to running VM to report buffer pool usage. 13 | */ 14 | 15 | public class MonBuffers { 16 | static final String CONNECTOR_ADDRESS = 17 | "com.sun.management.jmxremote.localConnectorAddress"; 18 | private static void logo() { 19 | System.out.println("Usage:java -cp $JAVA_HOME/lib/tools.jar:. MonBuffers [interval] [count]"); 20 | System.exit(1); 21 | } 22 | 23 | public static void main(String args[]) throws Exception { 24 | // attach to target VM to get connector address 25 | int sleep = 1000; 26 | int count = 10; 27 | 28 | if(args.length == 0) { 29 | logo(); 30 | } 31 | if (args.length > 1) { 32 | try { 33 | sleep = Integer.parseInt(args[1]); 34 | } catch(Exception e) { 35 | logo(); 36 | } 37 | } 38 | if (args.length >2) { 39 | try { 40 | count = Integer.parseInt(args[2]); 41 | } catch(Exception e) { 42 | logo(); 43 | } 44 | } 45 | System.out.println("start monitor DirectBuffer,pid:"+args[0]+",interval:"+sleep+",count:"+count); 46 | 47 | VirtualMachine vm = VirtualMachine.attach(args[0]); 48 | String connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS); 49 | if (connectorAddress == null) { 50 | // start management agent 51 | String agent = vm.getSystemProperties().getProperty("java.home") + 52 | File.separator + "lib" + File.separator + "management-agent.jar"; 53 | vm.loadAgent(agent); 54 | connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS); 55 | assert connectorAddress != null; 56 | } 57 | 58 | // connect to agent 59 | JMXServiceURL url = new JMXServiceURL(connectorAddress); 60 | JMXConnector c = JMXConnectorFactory.connect(url); 61 | MBeanServerConnection server = c.getMBeanServerConnection(); 62 | 63 | // get the list of pools 64 | Set mbeans = server.queryNames( 65 | new ObjectName("java.nio:type=BufferPool,*"), null); 66 | List pools = new ArrayList(); 67 | for (ObjectName name: mbeans) { 68 | BufferPoolMXBean pool = ManagementFactory 69 | .newPlatformMXBeanProxy(server, name.toString(), BufferPoolMXBean.class); 70 | pools.add(pool); 71 | } 72 | 73 | // print headers 74 | for (BufferPoolMXBean pool: pools) 75 | System.out.format(" %8s ", pool.getName()); 76 | System.out.println(); 77 | for (int i=0; i"; 6 | exit 1; 7 | fi 8 | p(){ 9 | if [ "$output_flag" = "1" ];then 10 | echo "$@" 11 | elif [ "$output_flag" = "2" ];then 12 | echo "$@" >>${pid}_jvm.log 13 | else 14 | echo "$@" |tee -a ${pid}_jvm.log 15 | fi 16 | } 17 | 18 | doMain(){ 19 | while(true) ;do 20 | i=1 21 | while read line ;do 22 | if [[ "$line" == "="* ]];then 23 | echo "$line" 24 | continue; 25 | elif [[ "$line" == "#"* ]];then 26 | echo "$line" 27 | continue; 28 | fi 29 | OLD_IFS="$IFS" 30 | IFS=";" 31 | defs=($line) 32 | IFS="$OLD_IFS" 33 | eval marker${i}=\'${defs[0]}\' 34 | eval cmd_def${i}=\'${defs[1]}\' 35 | eval desp_def${i}=\'${defs[2]}\' 36 | eval arg_def${i}=\'${defs[3]}\' 37 | eval arg_desp${i}=\'${defs[4]}\' 38 | eval def_arg${i}=\'${defs[5]}\' 39 | echo $i" : "$(eval echo \$desp_def${i})$(eval echo \$arg_desp${i}) 40 | ((i++)) 41 | done <$base_dir/jvm.lib/jvm.list 42 | echo "q : exit" 43 | echo -n "Enter command queue:" 44 | read input < /dev/tty 45 | if [ "$input" = "q" ];then exit 0;fi 46 | if [ "$input" = "" ];then continue;fi 47 | OLD_IFS=”$IFS” 48 | IFS=”\;” 49 | choices=($input) 50 | IFS=”$OLD_IFS” 51 | if [ "$input" = "q" ];then exit 0;fi 52 | cmd_queue=$input 53 | echo "which output type?" 54 | echo "default : console and log file(${1}_jvm.log)" 55 | echo "a : console only" 56 | echo "b : file only" 57 | echo -n "Enter output type:" 58 | read input < /dev/tty 59 | if [ "$input" = "a" ];then 60 | output_flag="1" 61 | elif [ "$input" = "b" ];then 62 | output_flag="2" 63 | else 64 | output_flag="" 65 | fi 66 | p "=======================================================" 67 | p "*** [`date "+%Y-%m-%d %H:%M:%S"`] start execute queue : $cmd_queue ***" 68 | for choice in ${choices[@]}; do 69 | OLD_IFS=”$IFS” 70 | IFS=”:” 71 | cmd=($choice) 72 | IFS=”$OLD_IFS” 73 | actul_cmd=$(eval echo \$cmd_def${cmd[0]}) 74 | actul_cmd=${actul_cmd//pid/$1} 75 | arg=${cmd[1]} 76 | if [ "$arg" = "" ];then 77 | arg="$(eval echo \$def_arg${cmd[0]})" 78 | fi 79 | arg=${arg//,/ } 80 | actul_cmd=${actul_cmd//\[arg\]/$arg} 81 | mark=$(eval echo \$marker${cmd[0]}) 82 | help_msg=`cat ${base_dir}/jvm.lib/jvm.dsc |grep -e "^${mark}:"|cut -c 3-` 83 | p "-------------------------------------------------------" 84 | p "*** [`date "+%Y-%m-%d %H:%M:%S"`] start execute command:$actul_cmd ***" 85 | p "*** [`date "+%Y-%m-%d %H:%M:%S"`] $(eval echo \$desp_def${cmd[0]}) ***" 86 | if [ ! "$help_msg" = "" ];then 87 | help_msg=`echo "$help_msg"|awk '{print "# "$0}'` 88 | p "# Help message:" 89 | p -e "$help_msg" 90 | p "" 91 | fi 92 | if [ "$output_flag" = "1" ];then 93 | bash -c "export WTOOL_JVM_LIB=${base_dir}/jvm.lib; $actul_cmd" 2>&1 94 | elif [ "$output_flag" = "2" ];then 95 | bash -c "export WTOOL_JVM_LIB=${base_dir}/jvm.lib; $actul_cmd" > ${pid}_jvm.log 2>&1 96 | else 97 | bash -c "export WTOOL_JVM_LIB=${base_dir}/jvm.lib; $actul_cmd" |tee -a ${pid}_jvm.log 98 | fi 99 | p "*** [`date "+%Y-%m-%d %H:%M:%S"`] finish execute command:$actul_cmd ***" 100 | done 101 | p "*** [`date "+%Y-%m-%d %H:%M:%S"`] finish execute queue ***" 102 | p "=======================================================" 103 | done 104 | } 105 | 106 | pid=$1 107 | base_dir=$(dirname "$(echo "$0" | sed -e '')") 108 | doMain "$@" 109 | -------------------------------------------------------------------------------- /tools/housemd.lib/README.md: -------------------------------------------------------------------------------- 1 | `HouseMD` 是一款非常敏捷的`Java`进程运行时的诊断调式命令行工具, 它具备安全易用高效的特点, 让它非常适合在要求严格的线上(生产)环境中使用. 2 | 3 | ## 特性 4 | 5 | - 交互式命令行 6 | - 支持`Tab`自动补全或候选列表提示 7 | - 支持命令历史 8 | - 查看加载类 9 | - 支持跟踪文件来源路径 10 | - 支持跟踪类加载器层次 11 | - 跟踪方法 12 | - 支持类短名字(`SimpleName`)和方法名(可选)限定跟踪目标 13 | - 支持根据抽象类或接口来限定其实现类的跟踪目标 14 | - 支持实时显示跟踪目标的摘要统计 15 | - 支持输出跟踪目标调用日志文件输出 16 | - 支持输出跟踪目标调用栈文件输出 17 | - 查看环境变量 18 | - 查看对象属性值 19 | 20 | ## 为什么要有`HouseMD` 21 | 22 | [点击这里查看](why-housemd) 23 | 24 | ## 与`BTrace` 相比 25 | 26 | - 仅一个`jar`包, 部署简单, 使用简单 27 | - 无需编写脚本, 借助指令完成常见诊断操作, 且切换快速高效 28 | - 借助命令行提示, 能够快速准确定位要跟踪的目标 29 | - 支持查看加载类的信息, 这在容器的应用诊断场景非常有用 30 | - 通过跟踪限时和限次数的机制, 来控制给跟踪过程带来的消耗 31 | - 自动检测并解决容器应用中类加载的问题 32 | - 自身源码精简短小, 易于阅读掌握, 易于定制扩展 33 | 34 | 35 | ## 安装 36 | 37 | - 安装 [jenv](https://github.com/linux-china/jenv) 38 | - 执行命令: `$ jenv install housemd` 39 | 40 | > 注意: 此版本尚不支持Windows, 欢迎你来帮大家实现. 41 | 42 | ## 入门 43 | 44 | ### 启动 45 | 46 | 47 | > housemd -h 48 | 49 | 你会看到`HouseMD`的帮助信息, 如下: 50 | 51 | Usage: housemd [OPTIONS] pid 52 | a runtime diagnosis tool of JVM. 53 | Options: 54 | -h, --help 55 | show help infomation of this command. 56 | -p, --port=[INT] 57 | set console local socket server port number. 58 | default: 54321 59 | Parameters: 60 | pid 61 | id of process to be diagnosing. 62 | 63 | 真正要用起来, 则需要拿到你要诊断的`java`进程的`ID`(通过`jps`或`ps`), 假设`pid`是`1234`, 然后执行: 64 | 65 | > housemd 1234 66 | 67 | ### 帮助 68 | 69 | 在显示若干行`INFO`信息后, 此时进入`HouseMD`的`Shell`提示符, 键入`help`指令, 可以查看其支持的内置指令: 70 | 71 | housemd> help 72 | 73 | quit terminate the process. 74 | help display this infomation. 75 | trace display or output infomation of method invocaton. 76 | loaded display loaded classes information. 77 | env display system env. 78 | inspect display fields of a class. 79 | 80 | 在`help`后加上指令的名字, 如`loaded`, 便会显示具体指令的帮助信息: 81 | 82 | housemd> help loaded 83 | Usage: loaded [OPTIONS] name 84 | display loaded classes information. 85 | Options: 86 | -h, --classloader-hierarchies 87 | display classloader hierarchies of loaded class. 88 | Parameters: 89 | name 90 | class name without package name. 91 | 92 | ### 退出 93 | 94 | 1. 键入`quit`指令 95 | 1. 键入`Ctrl + D` 96 | 97 | ### 告诉我们你在用 98 | 99 | 真诚的感谢每位关注和使用`HouseMD`的朋友, 若`HouseMD`有帮助到你, 那就请来[这儿留名](https://github.com/zhongl/HouseMD/issues/66), 说不定你还能发现你身边的朋友也在使用, 不如和他们一起来分享, 一起来吐槽, 一起来贡献. 100 | 101 | ### 常见问题解答 102 | 103 | [点击这里查看](FAQCN) 104 | 105 | ## 指令范例 106 | 107 | ### `loaded` 108 | 109 | housemd> loaded String 110 | java.lang.String -> /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/rt.jar 111 | 112 | > 查看类`java.lang.String`的加载路径 113 | 114 | housemd> loaded -h ScalaObject 115 | scala.ScalaObject -> /home/housemd/housemd.jar 116 | - com.github.zhongl.housemd.Duck$1@1e859c0 117 | - sun.misc.Launcher$AppClassLoader@1cde100 118 | - sun.misc.Launcher$ExtClassLoader@16f0472 119 | 120 | > 查看类`scala.ScalaObject`的加载路径和类加载器层次. 121 | 122 | > 注意: BootClassLoader由于不是`Java`语言实现, 所以不会显示. 123 | 124 | ### `trace` 125 | 126 | housemd> trace -t 2 TraceTarget$A.m 127 | INFO : probe class TraceTarget$A 128 | TraceTarget$A.m(int, String) TraceTarget$CL@42719c 0 -ms [Static Method] 129 | TraceTarget$A.m(String) TraceTarget$CL@42719c 2 3ms TraceTarget$A@401369 130 | 131 | TraceTarget$A.m(int, String) TraceTarget$CL@42719c 0 -ms [Static Method] 132 | TraceTarget$A.m(String) TraceTarget$CL@42719c 4 1ms TraceTarget$A@401369 133 | 134 | INFO : Ended by timeout 135 | INFO : reset class TraceTarget$A 136 | 137 | > 跟踪接下来2秒内`TraceTarget$A.m`方法的调用 138 | 139 | > 每列的含义依次是: 方法全名(含参数列表), 当前类的加载器对象, 总计调用次数, 平均调用耗时, 调用的自身对象 140 | 141 | housemd> trace -l 4 TraceTarget$D+.mD1 142 | INFO : probe class TraceTarget$D 143 | TraceTarget$D.mD1(int) TraceTarget$CL@42719c 2 <1ms TraceTarget$B@80cac9 144 | 145 | INFO : Ended by overlimit 146 | INFO : reset class TraceTarget$D 147 | 148 | > 跟踪接下来4次抽象类`TraceTarget$D`的`mD1`方法的调用 149 | 150 | > 这里可以看到, 这个方法调用的触发对象是其实现类`TraceTarget$B`的实例 151 | 152 | housemd> trace -i 4 TraceTarget$D+ 153 | INFO : probe class TraceTarget$D 154 | INFO : probe class TraceTarget$B 155 | TraceTarget$B.mC(String) TraceTarget$CL@42719c 8 <1ms TraceTarget$B@80cac9 156 | TraceTarget$B.mD2(int, int) TraceTarget$CL@42719c 8 <1ms TraceTarget$B@80cac9 157 | TraceTarget$D.mD1(int) TraceTarget$CL@42719c 8 <1ms TraceTarget$B@80cac9 158 | 159 | TraceTarget$B.mC(String) TraceTarget$CL@42719c 16 <1ms TraceTarget$B@80cac9 160 | TraceTarget$B.mD2(int, int) TraceTarget$CL@42719c 16 <1ms TraceTarget$B@80cac9 161 | TraceTarget$D.mD1(int) TraceTarget$CL@42719c 16 <1ms TraceTarget$B@80cac9 162 | 163 | INFO : Ended by timeout 164 | INFO : reset class TraceTarget$D 165 | INFO : reset class TraceTarget$B 166 | 167 | > 跟踪抽象类`TraceTarget$D`所有方法的调用, 设定每隔4秒进行一次实时摘要显示, 直至默认结束条件达成 168 | 169 | housemd> trace -d TraceTarget.addOne TraceTarget$A 170 | ...... 171 | INFO : You can get invocation detail from /tmp/trace/19987@hostname/detail 172 | 173 | > 跟踪`TraceTarget.addOne`方法和 `TraceTarget$A`的所有方法的调用, 并输出详细日志到文件 174 | 175 | 输出的`detail`文件内容: 176 | 177 | 2012-06-14 14:38:29 8ms [main] null TraceTarget.addOne [0] 1 178 | 2012-06-14 14:38:29 2ms [main] TraceTarget$A@995a79 TraceTarget$A.m [123] void 179 | 2012-06-14 14:38:30 0ms [main] null TraceTarget.addOne [0] 1 180 | 2012-06-14 14:38:30 0ms [main] TraceTarget$A@995a79 TraceTarget$A.m [123] void 181 | 182 | > 日志每行以一个空格分隔, 每列的含义依次是: 日期, 时间戳, 调用耗时, 调用线程名, 调用方法的自身对象, 调用方法全名, 调用方法参数值列表, 返回值(或异常) 183 | 184 | housemd> trace -s TraceTarget.addOne 185 | ...... 186 | INFO : You can get invocation stack from /tmp/trace/19987@hostname/stack 187 | 188 | > 跟踪`TraceTarget.addOne`方法, 并输出其调用栈到文件 189 | 190 | 输出的stack文件内容: 191 | 192 | TraceTarget.addOne(Integer) call by thread [main] 193 | TraceTarget.main(TraceTarget.java:42) 194 | 195 | ### `env` 196 | 197 | housemd> env USER 198 | USER = housemd 199 | 200 | > 查看环境变量`USER`的值 201 | 202 | housemd> env -e T.* 203 | TERM = xterm 204 | TYPESAFE_STACK_HOME = /home/housemd/ 205 | 206 | > 查看所有以`T`开头的环境变量的值 207 | 208 | ### `inspect` 209 | 210 | housemd> inspect -l 1 TraceTarget$B.s 211 | INFO : Probe class TraceTarget$B 212 | TraceTarget$B.s 123 TraceTarget$B@1687e7c TraceTarget$CL@42719c 213 | 214 | INFO : Ended by overlimit 215 | INFO : Reset class TraceTarget$B 216 | 217 | > 查看`TraceTarget$B`属性名为`s`的值 218 | 219 | > 实时显示的行记录每列的含义依次是: 属性全名, 属性值, 自身对象实例 , 此类的类加载器 220 | 221 | 更多信息请见[常见问题解答](FAQCN), 或指令帮助 222 | 223 | ## 编译打包 224 | 225 | 在此之前需要有: 226 | 227 | - JDK 6 228 | - [sbt](https://github.com/harrah/xsbt) 229 | 230 | 到命令行下, 执行下面的命令: 231 | 232 | $ git clone https://github.com/zhongl/HouseMD.git housemd 233 | $ cd housemd 234 | $ sbt proguard 235 | 236 | 在`target/scala-x.x.x/`会生成一个名为 `housemd_x.x.x-x.x.x.min.jar` 可执行包. 237 | 238 | 239 | ### 运行 240 | 241 | $ java -Xbootclasspath/a:$JAVA_HOME/lib/tools.jar -jar housemd_x.x.x-x.x.x.min.jar [OPTIONS] 242 | 243 | > 注意: 在 Mac OSX, `-Xbootclasspath` 可以不要. 244 | 245 | 246 | ## 历史版本和未来规划 247 | 248 | [点击这里查看](../issues/milestones) 249 | 250 | ## 疑问, 建议, 缺陷 251 | 252 | 欢迎任何疑问, 建议还有缺陷, 请至[这里](https://github.com/zhongl/HouseMD/issues/new)提交给我. 253 | 254 | ## 参与贡献 255 | 256 | 请仔细阅读[开发指南](DevGuideCN). 257 | 258 | ## 后记 259 | 260 | `HouseMD`是基于字节码技术的诊断工具, 因此除了`Java`以外, 任何最终以字节码形式运行于`JVM`之上的语言, `HouseMD`都支持对它们进行诊断, 如`Clojure`(感谢[@Killme2008](http://fnil.net/)提供了它的[使用入门]((http://www.blogjava.net/killme2008/archive/2012/06/15/380822.html))), `scala`, `Groovy`, `JRuby`, `Jython`, `kotlin`等. 261 | 262 | 263 | 感谢使用`HouseMD`, 祝你玩得开心~ :) 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __ __ _____ _ 2 | / / /\ \ \/__ \___ ___ | | 3 | \ \/ \/ / / /\/ _ \ / _ \| | 4 | \ /\ / / / | (_) | (_) | | 5 | \/ \/ \/ \___/ \___/|_| 6 | # wtool java工具包 7 | 8 | ## java命令 9 | * `wtool housemd pid [java_home]` 10 | > 使用housemd对java程序进行运行时跟踪,支持的操作有: 11 | > > - 查看加载类 12 | > > - 跟踪方法 13 | > > - 查看环境变量 14 | > > - 查看对象属性值 15 | > 16 | > 详细信息请参考[housemd说明文档](https://github.com/qdaxb/wtool/raw/master/java/tools/housemd.lib/README.md) 17 | 18 | * `wtool jarconfict path` 19 | > 查找jar包间冲突的类 20 | 21 | * `wtool jarfind classname path` 22 | > 在jar包中查找类名 23 | 24 | * `wtool jargrep "text" ` 25 | > 在jar包中查找文本,可查找常量字符串、类引用。 26 | 27 | * `wtool findcycle [path]` 28 | > 查找当前工程中是否存在循环引用(目前仅支持maven工程,默认为当前路径) 29 | 30 | * `wtool jvm pid` 31 | > 执行jvm debug工具,包含对java栈、堆、线程、gc等状态的查看。 32 | > > * 进入jvm工具后可以输入序号执行对应命令 33 | > > * 可以一次执行多个命令,用分号";"分隔,如:1;3;4;5;6 34 | > > * 每个命令可以带参数,用冒号":"分隔,同一命令的参数之间用逗号分隔,如:1:1000,100;3;5:/data1/output.bin 35 | > 36 | > 示例 37 | 38 | ``` 39 | 进入wtool jvm工具: 40 | [root@localhost ~]# wtool jvm 31395 41 | ========线程相关======= 42 | 1 : 查看占用cpu最高的线程情况 43 | 2 : 打印所有线程 44 | 3 : 打印线程数 45 | 4 : 按线程状态统计线程数 46 | ========GC相关======= 47 | 5 : 垃圾收集统计(包含原因)可以指定间隔时间及执行次数,默认10秒 48 | 6 : 显示堆中各代的空间1000 5 49 | 7 : 垃圾收集统计。可以指定间隔时间及执行次数,默认10秒 50 | 8 : 打印perm区内存情况*会使程序暂停响应* 51 | 9 : 查看directbuffer情况 52 | ========堆对象相关======= 53 | 10 : dump heap到文件*会使程序暂停响应*默认保存到`pwd`/dump.bin,可指定其它路径 54 | 11 : 触发full gc。*会使程序暂停响应* 55 | 12 : 打印jvm heap统计*会使程序暂停响应* 56 | 13 : 打印jvm heap中top20的对象。*会使程序暂停响应*参数:1:按实例数量排序,2:按内存占用排序,默认为1 57 | 14 : 触发full gc后打印jvm heap中top20的对象。*会使程序暂停响应*参数:1:按实例数量排序,2:按内存占用排序,默认为1 58 | 15 : 输出所有类装载器在perm里产生的对象。可以指定间隔时间及执行次数 59 | ========其它======= 60 | 16 : 打印finalzer队列情况 61 | 17 : 显示classloader统计 62 | 18 : 显示jit编译统计 63 | 19 : 死锁检测 64 | 20 : 等待X秒,默认为1 65 | q : exit 66 | Enter command queue: 67 | 68 | 比如说我要查看线程运行状态、看5秒gc状态、触发fullgc、之后再观察线程状态、10秒gc状态、最后打印heap中所有对象数量,可以输入命令序列: 69 | 70 | 4;5:1000,5;11;4;5;12 71 | 72 | 之后会提示: 73 | which output type? 74 | default : console and log file(31395_jvm.log) 75 | a : console only 76 | b : file only 77 | Enter output type: 78 | 程序可以只输出到控制台、只输出到文件。默认是同时输出到两者,此时直接按回车。 79 | 80 | 输出结果会同时打印到控制台和当前目录的$pid_jvm.log中,会输出每个命令行、时间信息和一些关于这个命令的帮助信息,比如刚才的例子,输出如下: 81 | 82 | ======================================================= 83 | *** [2014-12-03 17:52:10] start execute queue : 3;4:1000,5;6;3;4;5 *** 84 | ------------------------------------------------------- 85 | *** [2014-12-03 17:52:10] start execute command:jstack 31395 |grep 'java.lang.Thread.State:'|sort|uniq -c *** 86 | *** [2014-12-03 17:52:10] 打印线程运行状态统计 *** 87 | 18 java.lang.Thread.State: RUNNABLE 88 | 62 java.lang.Thread.State: TIMED_WAITING (on object monitor) 89 | 66 java.lang.Thread.State: TIMED_WAITING (parking) 90 | 31 java.lang.Thread.State: TIMED_WAITING (sleeping) 91 | 14 java.lang.Thread.State: WAITING (on object monitor) 92 | 40 java.lang.Thread.State: WAITING (parking) 93 | *** [2014-12-03 17:52:10] finish execute command:jstack 31395 |grep 'java.lang.Thread.State:'|sort|uniq -c *** 94 | ------------------------------------------------------- 95 | *** [2014-12-03 17:52:10] start execute command:jstat -gcutil 31395 1000 5 *** 96 | *** [2014-12-03 17:52:10] 垃圾收集统计。 *** 97 | # Help message: 98 | # Column | Description 99 | # S0 | S0使用百分比 100 | # S1 | S1使用百分比 101 | # E | eden使用百分比 102 | # O | old使用百分比 103 | # P | perm使用百分比 104 | # YGC | 年轻代gc次数 105 | # YGCT | 年轻代gc时间 106 | # FGC | full gc次数 107 | # FGCT | full gc时间 108 | # GCT | 垃圾收集总时间 109 | 110 | S0 S1 E O P YGC YGCT FGC FGCT GCT 111 | 0.00 74.11 31.12 16.11 32.16 2623 183.616 55 13.659 197.276 112 | 0.00 74.11 31.49 16.11 32.16 2623 183.616 55 13.659 197.276 113 | 0.00 74.11 31.54 16.11 32.16 2623 183.616 55 13.659 197.276 114 | 0.00 74.11 31.54 16.11 32.16 2623 183.616 55 13.659 197.276 115 | 0.00 74.11 31.64 16.11 32.16 2623 183.616 55 13.659 197.276 116 | *** [2014-12-03 17:52:14] finish execute command:jstat -gcutil 31395 1000 5 *** 117 | ------------------------------------------------------- 118 | *** [2014-12-03 17:52:14] start execute command:jmap -histo:live 31395 >/dev/null *** 119 | *** [2014-12-03 17:52:14] 触发full gc。*会使程序暂停响应* *** 120 | *** [2014-12-03 17:52:15] finish execute command:jmap -histo:live 31395 >/dev/null *** 121 | ------------------------------------------------------- 122 | *** [2014-12-03 17:52:15] start execute command:jstack 31395 |grep 'java.lang.Thread.State:'|sort|uniq -c *** 123 | *** [2014-12-03 17:52:15] 打印线程运行状态统计 *** 124 | 18 java.lang.Thread.State: RUNNABLE 125 | 62 java.lang.Thread.State: TIMED_WAITING (on object monitor) 126 | 66 java.lang.Thread.State: TIMED_WAITING (parking) 127 | 31 java.lang.Thread.State: TIMED_WAITING (sleeping) 128 | 14 java.lang.Thread.State: WAITING (on object monitor) 129 | 40 java.lang.Thread.State: WAITING (parking) 130 | *** [2014-12-03 17:52:16] finish execute command:jstack 31395 |grep 'java.lang.Thread.State:'|sort|uniq -c *** 131 | ------------------------------------------------------- 132 | *** [2014-12-03 17:52:16] start execute command:jstat -gcutil 31395 1000 10 *** 133 | *** [2014-12-03 17:52:16] 垃圾收集统计。 *** 134 | # Help message: 135 | # Column | Description 136 | # S0 | S0使用百分比 137 | # S1 | S1使用百分比 138 | # E | eden使用百分比 139 | # O | old使用百分比 140 | # P | perm使用百分比 141 | # YGC | 年轻代gc次数 142 | # YGCT | 年轻代gc时间 143 | # FGC | full gc次数 144 | # FGCT | full gc时间 145 | # GCT | 垃圾收集总时间 146 | 147 | S0 S1 E O P YGC YGCT FGC FGCT GCT 148 | 0.00 0.00 0.69 13.26 32.16 2623 183.616 56 14.009 197.625 149 | 0.00 0.00 0.93 13.26 32.16 2623 183.616 56 14.009 197.625 150 | 0.00 0.00 0.94 13.26 32.16 2623 183.616 56 14.009 197.625 151 | 0.00 0.00 0.95 13.26 32.16 2623 183.616 56 14.009 197.625 152 | 0.00 0.00 1.18 13.26 32.16 2623 183.616 56 14.009 197.625 153 | 0.00 0.00 1.19 13.26 32.16 2623 183.616 56 14.009 197.625 154 | 0.00 0.00 1.40 13.26 32.16 2623 183.616 56 14.009 197.625 155 | 0.00 0.00 1.44 13.26 32.16 2623 183.616 56 14.009 197.625 156 | 0.00 0.00 1.72 13.26 32.16 2623 183.616 56 14.009 197.625 157 | 0.00 0.00 2.03 13.26 32.16 2623 183.616 56 14.009 197.625 158 | *** [2014-12-03 17:52:25] finish execute command:jstat -gcutil 31395 1000 10 *** 159 | ------------------------------------------------------- 160 | *** [2014-12-03 17:52:25] start execute command:jmap -histo 31395 *** 161 | *** [2014-12-03 17:52:25] 打印jvm heap中对象统计*会使程序暂停响应* *** 162 | # Help message: 163 | # class name对应的就是Class文件里的class的标识 164 | # B代表byte 165 | # C代表char 166 | # D代表double 167 | # F代表float 168 | # I代表int 169 | # J代表long 170 | # Z代表boolean 171 | # 前边有[代表数组 172 | # 对象用[L+类名表示 173 | 174 | 175 | num #instances #bytes class name 176 | ---------------------------------------------- 177 | 1: 18471 14269744 [B 178 | 2: 70122 11729664 179 | 3: 112468 10395568 [C 180 | 4: 70122 9546512 181 | 5: 6038 7591760 182 | 6: 6038 4535144 183 | 7: 4979 4205760 184 | 8: 42984 2750976 com.mysql.jdbc.ConnectionPropertiesImpl$BooleanConnectionProperty 185 | 9: 108153 2595672 java.lang.String 186 | 10: 70458 2254656 java.util.Hashtable$Entry 187 | 11: 15578 2033232 [I 188 | 12: 2706 1596504 189 | 13: 9831 1293752 [Ljava.lang.Object; 190 | 14: 37742 1207744 java.util.HashMap$Entry 191 | 15: 14756 1139224 [Ljava.util.HashMap$Entry; 192 | 16: 6557 795984 java.lang.Class 193 | 17: 1231 745536 [Ljava.util.Hashtable$Entry; 194 | 18: 10348 745056 com.mysql.jdbc.ConnectionPropertiesImpl$IntegerConnectionProperty 195 | 19: 11542 738688 com.mysql.jdbc.ConnectionPropertiesImpl$StringConnectionProperty 196 | 20: 8186 654880 java.lang.reflect.Method 197 | 21: 9148 653312 [S 198 | 22: 16283 651320 java.util.LinkedHashMap$Entry 199 | ………………省略…………………… 200 | Total 906014 92399120 201 | *** [2014-12-03 17:52:25] finish execute command:jmap -histo 31395 *** 202 | *** [2014-12-03 17:52:25] finish execute queue *** 203 | ======================================================= 204 | ``` 205 | --------------------------------------------------------------------------------