-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0598121
Showing
25 changed files
with
1,909 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2018 xdc0209 | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
![kiwi and kiwi bird](images/kiwi_and_kiwi_bird.png) | ||
|
||
# 工具简介 | ||
|
||
Kiwi Java 工具箱。 | ||
|
||
本工具用于定位**内存泄漏**、**CPU占用过高**等问题。 | ||
|
||
# 功能说明 | ||
|
||
1. 一键式收集Java进程的问题定位信息,包括:Thread Stack和Heap Dump等。 | ||
2. 监控Java进程使用的CPU,当占用率过高时,自动收集Top信息和Thread Stack。 | ||
3. 启动、停止、查询、导出飞行记录。 | ||
|
||
# 使用方法 | ||
|
||
将工具上传到Linux服务器的任意目录,并进行解压。 | ||
|
||
注意:须确保Java进程用户有权限访问此工具目录。推荐放在/home目录,这个目录一般是满足权限要求的。 | ||
|
||
1. 解压:unzip -o kiwi_java_toolbox-*.zip -d /home | ||
2. 收集信息:sh /home/kiwi_java_toolbox/java_info_collect.sh | ||
3. 监控进程:sh /home/kiwi_java_toolbox/java_cpu_monitor.sh.sh | ||
4. 飞行记录:sh /home/kiwi_java_toolbox/java_flight_recorder.sh | ||
|
||
# 后续步骤 | ||
|
||
1. 内存泄露:使用工具分析Head Dump,[本地工具MemoryAnalyzer](http://www.eclipse.org/mat)。 | ||
2. CPU过高:使用工具分析Thread Stack,[在线工具](http://gceasy.io/index.jsp),[本地工具IBM_Thread_and_Monitor_Dump_Analyzer_for_Java](https://www.ibm.com/developerworks/community/groups/service/html/communitystart?communityUuid=2245aa39-fa5c-4475-b891-14c205f7333c)。 | ||
3. 性能优化:使用工具分析飞行记录,[本地工具Java Mission Control](http://www.oracle.com/technetwork/java/javaseproducts/mission-control/index.html)。 | ||
|
||
# Java常见问题 | ||
|
||
1. 是否存在线程的占用cpu过高。根据jstack和top的结果判断。 | ||
2. 是否存在多线程使用HashMap而导致的死循环。查看不同时间的多个线程堆栈,是否存在相同的java.util.HashMap堆栈。 | ||
3. 是否存在数据库连接池枯竭的问题。查看堆栈中org.apache.commons.pool.impl.GenericObjectPool.borrowObject的数量是否大于5个(不绝对,只是参考值)。 | ||
4. 是否存在死锁。查看堆栈中是否包含deadlock关键字。 | ||
5. 是否存在内存溢出。查看GC信息中老年代Old区占用百分比是否维持在95%以上(不绝对,只是初步判断)。 | ||
|
||
# 附1:核心原理 | ||
|
||
本工具底层仍然是Jdk的工具,只是对常见的手工的操作流程的自动化封装。 | ||
|
||
很多公司的产品是跑在Jre上的,而不是Jdk上。定位问题的时候需要上传Jdk。 | ||
|
||
由于Jdk的文件体积比较大,而且包含了很多定位问题不需要的东西,本工具对Jdk进行了提取: | ||
|
||
1. libattach.so:连接Java进程使用的类库。 | ||
2. tools.jar:Jdk中工具包。 | ||
|
||
为了达到最大兼容性,本工具对Jdk的使用采用如下逻辑: | ||
|
||
1. 如果Java进程是Jdk,则使用Java进程的Jdk。 | ||
2. 如果Java进程是Jre,则使用本工具内置的Jdk。另外,本工具也进行版本校验,如果Java进程和内置的Jdk的大版本不一致,则直接提示并退出。 | ||
|
||
# 附2:JDK工具使用注意事项 | ||
|
||
1. 常见错误1: | ||
|
||
问题原因:当前操作系统用户与Java进程用户不一致,导致权限不够。 | ||
解决方案:请合理修改工具目录的属主和权限,并且切换用户。 | ||
|
||
```java | ||
java.io.IOException: well-known file is not secure | ||
at sun.tools.attach.LinuxVirtualMachine.checkPermissions(Native Method) | ||
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:117) | ||
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63) | ||
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:213) | ||
at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:140) | ||
at sun.tools.jcmd.JCmd.main(JCmd.java:129) | ||
``` | ||
|
||
2. 常见错误2: | ||
|
||
问题原因:Jdk工具的的版本与Java进程的版本不一致。 | ||
解决方案:请使用匹配版本的Jdk。一般情况下,大版本匹配就可以了(如1.8.0_111与1.8.0_162的大版本都是1.8.0),不行的话就要精确匹配。 | ||
更新本工具的Jdk库:sh kiwi_java_toolbox/lib/jdk_lib_extract.sh <jdk_path> | ||
查询本工具的Jdk库:vi kiwi_java_toolbox/lib/jdk-*/tools.jar --> com\sun\tools\javac\resources\version.class | ||
|
||
```java | ||
com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded | ||
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106) | ||
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63) | ||
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:213) | ||
at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:140) | ||
at sun.tools.jcmd.JCmd.main(JCmd.java:129) | ||
``` | ||
|
||
3. 常见错误3: | ||
|
||
问题原因:在java.library.path未找到libattach.so。 | ||
解决方案:正确设置java的启动参数:-Djava.library.path=<java_library_path>。 | ||
|
||
```java | ||
java.lang.UnsatisfiedLinkError: no attach in java.library.path | ||
at java.lang.ClassLoader.loadLibrary(Unknown Source) | ||
at java.lang.Runtime.loadLibrary0(Unknown Source) | ||
at java.lang.System.loadLibrary(Unknown Source) | ||
at sun.tools.attach.LinuxVirtualMachine.<clinit>(LinuxVirtualMachine.java:336) | ||
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63) | ||
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:213) | ||
at sun.tools.jstack.JStack.runThreadDump(JStack.java:159) | ||
at sun.tools.jstack.JStack.main(JStack.java:112) | ||
``` | ||
|
||
# 捐赠 | ||
|
||
如果你觉得Kiwi对你有帮助,或者想对我微小工作的一点资瓷,欢迎给我捐赠。 | ||
|
||
<img src="images/qrcode_alipay.jpg"><img src="images/qrcode_wechat.jpg"> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#!/bin/bash | ||
|
||
# ----------------------------- kiwi bash lib start ------------------------------------- | ||
|
||
# Make sure to execute this script with bash. Bash works well on suse, redhat, aix.## | ||
# 确保以bash执行此脚本。Bash在suse、redhat、aix上表现很出色。## | ||
[ -z "$BASH" ] && echo "Please use bash to run this script [ bash $0 ] or make sure the first line of this script [ $0 ] is [ #!/bin/bash ]." && exit 1 | ||
|
||
# Set the bash debug info style to pretty format. +[T: <Time>, L: <LineNumber>, S: <ScriptName>, F: <Function>]## | ||
# 设置bash的调试信息为漂亮的格式。+[T: <Time>, L: <LineNumber>, S: <ScriptName>, F: <Function>]## | ||
[ -c /dev/stdout ] && export PS4_COLOR="32" | ||
[ ! -c /dev/stdout ] && export PS4_COLOR="" | ||
export PS4='+[$(debug_info=$(printf "T: %s, L:%3s, S: %s, F: %s" "$(date +%H%M%S)" "$LINENO" "$(basename $(cd $(dirname ${BASH_SOURCE[0]}) && pwd))/$(basename ${BASH_SOURCE[0]})" "$(for ((i=${#FUNCNAME[*]}-1; i>=0; i--)) do func_stack="$func_stack ${FUNCNAME[i]}"; done; echo $func_stack)") ; [ -z "$PS4_COLOR" ] && echo ${debug_info:0:94} ; [ -n "$PS4_COLOR" ] && echo -e "\e[${PS4_COLOR}m${debug_info:0:80}\e[0m")]: ' | ||
|
||
# 保存调试状态,用于调用子脚本。调用子脚本样例:bash $DEBUG_SWITCH subscript.sh## | ||
# Save the debug state to invoke the subscript. Invoke the subscript example: bash $DEBUG_SWITCH subscript.sh## | ||
(echo "${SHELLOPTS}" | grep -q "xtrace") && export DEBUG_SWITCH=-x | ||
|
||
# Get the absolute path of this script.## | ||
# 获取脚本的绝对路径。## | ||
BASE_DIR=$(cd $(dirname $0) && pwd) | ||
BASE_NAME=$(basename $0 .sh) | ||
|
||
# 设置日志文件。## | ||
# Set the log file.## | ||
log=$BASE_DIR/$BASE_NAME.log | ||
|
||
function print_error() | ||
{ | ||
echo "[$(date "+%F %T")] ERROR: $*" | tee -a $log 1>&2 | ||
} | ||
|
||
function print_info() | ||
{ | ||
echo "[$(date "+%F %T")] INFO: $*" | tee -a $log | ||
} | ||
|
||
function log_error() | ||
{ | ||
[ -n "$log" ] && echo "[$(date "+%F %T")] ERROR: $*" >>$log | ||
} | ||
|
||
function log_info() | ||
{ | ||
[ -n "$log" ] && echo "[$(date "+%F %T")] INFO: $*" >>$log | ||
} | ||
|
||
function die() | ||
{ | ||
print_error "$*" | ||
print_error "See log [ $log ] for details." | ||
exit 1 | ||
} | ||
|
||
# ----------------------------- kiwi bash lib end --------------------------------------- | ||
|
||
function get_target_java_pid() | ||
{ | ||
for arg in $(echo $@) | ||
do | ||
# 分析了jdk的工具集,第一个数字参数即为java的pid。## | ||
(echo $arg | grep -w '^[0-9][0-9]*$' >/dev/null 2>&1) && echo $arg && return 0 | ||
done | ||
} | ||
|
||
function check_target_java_pid() | ||
{ | ||
local target_java_pid=$1 | ||
|
||
# 如果target_java_pid为空,则不进行检查。正常的jdk工具使用过程中是有不输入参数的场景的,例如使用无参数的jps查询当前运行的所有java进程。## | ||
[ -z "$target_java_pid" ] && return 0 | ||
|
||
# 检查pid是否合法。## | ||
ps -ww -o pid,user:20,cmd -p $target_java_pid | sed '1d' | awk '{print $3}' | grep -w java >/dev/null 2>&1 | ||
[ $? -ne 0 ] && echo "Pid [ $target_java_pid ] is not a valid java pid." && exit 1 | ||
|
||
# jdk提供的工具执行时,要求当前操作系统用户和java进程用户一致,否则会报错。## | ||
local cur_user=$(whoami) | ||
local java_user=$(ps -ww -o pid,user:20,cmd -p $target_java_pid | sed '1d' | awk '{print $2}') | ||
[ "$cur_user" != "$java_user" ] && echo "Current os user [ $cur_user ] and java process user [ $java_user ] don't match. Please switch the user to [ $java_user ]." && exit 1 | ||
} | ||
|
||
function set_java_env_var() | ||
{ | ||
local target_java_pid=$1 | ||
|
||
# 获取JAVA_CMD_FOR_TOOLS的逻辑:## | ||
# 1. 如果target_java_pid不空,则使用target_java_pid获取java执行程序路径,进而设置JAVA_CMD_FOR_TOOLS。## | ||
# 2. 如果target_java_pid为空,且环境变量JAVA_CMD_FOR_TOOLS已设置,则以环境变量为准。## | ||
# 3. 如果target_java_pid为空,且环境变量JAVA_CMD_FOR_TOOLS未设置,则提示设置环境变量,并退出。## | ||
[ -n "$target_java_pid" ] && JAVA_CMD_FOR_TOOLS=$(readlink -m /proc/$target_java_pid/exe) | ||
|
||
# 如果JAVA_CMD_FOR_TOOLS仍为空,则提示设置环境变量,并退出。## | ||
if [ -z "$JAVA_CMD_FOR_TOOLS" ]; then | ||
# 查找正在运行的java进程。## | ||
local java_processes=$(ps -eww -o pid,user:20,cmd | grep -v grep | grep java) | ||
[ -z "$java_processes" ] && echo "Find no running java process." && exit 1 | ||
|
||
# 提示设置JAVA_CMD_FOR_TOOLS。## | ||
echo "Env var [ JAVA_CMD_FOR_TOOLS ] not set. Please set env var first by using one of the following commands: " | ||
printf "%-10s %-20s %s\n" "PID" "USER" "SET_JAVA_ENV_COMMAND" | ||
echo "$java_processes" | while read java_process | ||
do | ||
java_process_id=$(echo $java_process | awk '{print $1}') | ||
java_process_user=$(echo $java_process | awk '{print $2}') | ||
java_process_executable=$(readlink -m /proc/$java_process_id/exe) | ||
printf "%-10s %-20s [ export JAVA_CMD_FOR_TOOLS=%s ]\n" "$java_process_id" "$java_process_user" "$java_process_executable" | ||
done | ||
exit 1 | ||
fi | ||
|
||
if [ -f "$(dirname $JAVA_CMD_FOR_TOOLS)/../lib/tools.jar" ]; then | ||
# JAVA_CMD_FOR_TOOLS是jdk,使用此jdk的类库,这样可以减小发生类库版本不匹配的可能性。## | ||
JAVA_LIBRARY_PATH_OPTION="" | ||
JAVA_CLASSPATH_OPTION="-classpath $(dirname $JAVA_CMD_FOR_TOOLS)/../lib/tools.jar" | ||
else | ||
# JAVA_CMD_FOR_TOOLS是jre,使用本工具中的类库。## | ||
|
||
# 检查对应版本的jdk类库是否存在。## | ||
local java_version=$($JAVA_CMD_FOR_TOOLS -version 2>&1 | grep 'java version' | awk -F'"' '{print $2}' | awk -F'_' '{print $1}') | ||
[ ! -d "$BASE_DIR/../lib/jdk-$java_version" ] && echo "Find no jdk lib dir [ $BASE_DIR/../lib/jdk-$java_version ] for java [ $JAVA_CMD_FOR_TOOLS $java_version ]." && exit 1 | ||
|
||
# 使用对应版本的jdk类库建立链接。## | ||
JAVA_LIBRARY_PATH_OPTION="-Djava.library.path=$BASE_DIR/../lib/jdk-$java_version" | ||
JAVA_CLASSPATH_OPTION="-classpath $BASE_DIR/../lib/jdk-$java_version/tools.jar" | ||
fi | ||
} | ||
|
||
# 获取pid。## | ||
target_java_pid=$(get_target_java_pid $@) | ||
|
||
# 校验pid。## | ||
check_target_java_pid $target_java_pid | ||
|
||
# 设置java环境变量。## | ||
set_java_env_var $target_java_pid | ||
|
||
# 执行命令。## | ||
$JAVA_CMD_FOR_TOOLS $JAVA_LIBRARY_PATH_OPTION $JAVA_CLASSPATH_OPTION sun.tools.jcmd.JCmd $@ |
Oops, something went wrong.