Skip to content

Latest commit

 

History

History
381 lines (304 loc) · 18.1 KB

java.md

File metadata and controls

381 lines (304 loc) · 18.1 KB

🐌 Java相关脚本

用于快速排查JavaCPU性能问题(top us值过高),自动查出运行的Java进程中消耗CPU多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用。
目前只支持Linux。原因是MacWindowsps命令不支持列出线程,更多信息参见#33,欢迎提供解法。

PS,如何操作可以参见@bluedavy的《分布式Java应用》的【5.1.1 cpu消耗分析】一节,说得很详细:

  1. top命令找出有问题Java进程及线程id
    1. 开启线程显示模式
    2. CPU使用率排序
    3. 记下Java进程id及其CPU高的线程id
  2. 用进程id作为参数,jstack有问题的Java进程
  3. 手动转换线程id成十六进制(可以用printf %x 1234
  4. 查找十六进制的线程id(可以用grep
  5. 查看对应的线程栈

查问题时,会要多次这样操作以确定问题,上面过程太繁琐太慢了

用法

show-busy-java-threads [Options] [delay [ucount]]
# 从 所有的 Java进程中找出最消耗CPU的线程(缺省5个),打印出其线程栈。

show-busy-java-threads -c <要显示的线程栈数>

show-busy-java-threads -c <要显示的线程栈数> -p <指定的Java Process>

# -F选项:执行jstack命令时加上-F选项(强制jstack),一般情况不需要使用
show-busy-java-threads -p <指定的Java Process> -F

show-busy-java-threads -s <指定jstack命令的全路径>
# 对于sudo方式的运行,JAVA_HOME环境变量不能传递给root,
# 而root用户往往没有配置JAVA_HOME且不方便配置,
# 显式指定jstack命令的路径就反而显得更方便了

show-busy-java-threads -a <输出记录到的文件>

show-busy-java-threads -c <要显示的线程栈数> -p <指定的Java Process> <刷新间隔秒数> <刷新次数>

##############################
# 注意:
##############################
# 如果Java进程的用户 与 执行脚本的当前用户 不同,则jstack不了这个Java进程。
# 为了能切换到Java进程的用户,需要加sudo来执行,即可以解决:
sudo show-busy-java-threads

# 帮助信息
$ show-busy-java-threads -h
Usage: show-busy-java-threads [OPTION] [delay [ucount]]
Find out the highest cpu consumed threads of java, and print the stack of these threads.
Example: show-busy-java-threads -c 10

Options:
    -p, --pid       find out the highest cpu consumed threads from the specifed java process,
                    default from all java process.
    -c, --count     set the thread count to show, default is 5
    -a, --append-file <file>  specify the file to append output as log
    -s, --jstack-path <path>  specify the path of jstack command
    -F, --force               set jstack to force a thread dump(use jstack -F option)
    -S, --jstack-file-dir <path>  specifies the directory for storing jstack output files, and keep files.
                            default store jstack output files at tmp dir, and auto remove after run.
                            use this option to keep files so as to review jstack later.
    -m, --mix-native-frames   set jstack to print both java and native frames (mixed mode).
    -l, --lock-info           set jstack with long listing. Prints additional information about locks.
    -d, --top-delay  <deplay> specifies the delay between top samples, default is 0.5 (second).
                              get thread cpu percentage during this delay interval.
                              more info see top -d option. eg: -d 1 (1 second).
    -P, --use-ps              use ps command to find busy thread(cpu usage) instead of top command,
                              default use top command, because cpu usage of ps command is expressed as
                              the percentage of time spent running during the entire lifetime of a process,
                              this is not ideal.
    -h, --help      display this help and exit
    delay is the delay between updates in seconds. when this is not set, it will output once.
    ucount is the number of updates. when delay is set, ucount is not set, it will output in unstop mode.

示例

$ show-busy-java-threads
[1] Busy(57.0%) thread(23355/0x5b3b) stack of java process(23269) under user(admin):
"pool-1-thread-1" prio=10 tid=0x000000005b5c5000 nid=0x5b3b runnable [0x000000004062c000]
   java.lang.Thread.State: RUNNABLE
    at java.text.DateFormat.format(DateFormat.java:316)
    at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
    at com.xxx.fooared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:127)
    at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)

[2] Busy(26.1%) thread(24018/0x5dd2) stack of java process(23269) under user(admin):
"pool-1-thread-2" prio=10 tid=0x000000005a968800 nid=0x5dd2 runnable [0x00000000420e9000]
   java.lang.Thread.State: RUNNABLE
    at java.util.Arrays.copyOf(Arrays.java:2882)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:572)
    at java.lang.StringBuffer.append(StringBuffer.java:320)
    - locked <0x00000007908d0030> (a java.lang.StringBuffer)
    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:890)
    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:869)
    at java.text.DateFormat.format(DateFormat.java:316)
    at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
    at com.xxx.fooared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:126)
    at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
...

上面的线程栈可以看出,CPU消耗最高的2个线程都在执行java.text.DateFormat.format,业务代码对应的方法是shared.monitor.schedule.AppMonitorDataAvgScheduler.run。可以基本确定:

  • AppMonitorDataAvgScheduler.run调用DateFormat.format次数比较频繁。
  • DateFormat.format比较慢。(这个可以由DateFormat.format的实现确定。)

多个执行几次show-busy-java-threads,如果上面情况高概率出现,则可以确定上面的判定。
# 因为调用越少代码执行越快,则出现在线程栈的概率就越低。

分析shared.monitor.schedule.AppMonitorDataAvgScheduler.run实现逻辑和调用方式,以优化实现解决问题。

贡献者

  • silentforce改进此脚本,增加对环境变量JAVA_HOME的判断。 #15
  • liuyangc3
    • 发现并解决jstack非当前用户Java进程的问题。 #50
    • 优化性能,通过read -a简化反复的awk操作。 #51

找出Java LibJava库,即Jar文件)或Class目录(类目录)中的重复类。
全系统支持(Python实现,安装Python即可),如LinuxMacWindows

Java开发的一个麻烦的问题是Jar冲突(即多个版本的Jar),或者说重复类。会出NoSuchMethod等的问题,还不见得当时出问题。找出有重复类的Jar,可以防患未然。

用法

  • 通过脚本参数指定Libs目录,查找目录下Jar文件,收集Jar文件中Class文件以分析重复类。可以指定多个Libs目录。
    注意,只会查找这个目录下Jar文件,不会查找子目录下Jar文件。因为Libs目录一般不会用子目录再放Jar,这样也避免把去查找不期望Jar
  • 通过-c选项指定Class目录,直接收集这个目录下的Class文件以分析重复类。可以指定多个Class目录。
# 查找当前目录下所有Jar中的重复类
show-duplicate-java-classes

# 查找多个指定目录下所有Jar中的重复类
show-duplicate-java-classes path/to/lib_dir1 /path/to/lib_dir2

# 查找多个指定Class目录下的重复类。 Class目录 通过 -c 选项指定
show-duplicate-java-classes -c path/to/class_dir1 -c /path/to/class_dir2

# 查找指定Class目录和指定目录下所有Jar中的重复类的Jar
show-duplicate-java-classes path/to/lib_dir1 /path/to/lib_dir2 -c path/to/class_dir1 -c path/to/class_dir2

JDK开发场景使用说明

Maven作为构建工程示意过程。

对于一般的工程
# 在项目模块目录下执行,拷贝依赖Jar到目录target/dependency下
$ mvn dependency:copy-dependencies -DincludeScope=runtime
...
# 检查重复类
$ show-duplicate-java-classes target/dependency
...
对于Web工程

对于Web工程,即war maven模块,会打包生成war文件。

# 在war模块目录下执行,生成war文件
$ mvn install
...
# 解压war文件,war文件中包含了应用的依赖的Jar文件
$ unzip target/*.war -d target/war
...
# 检查重复类
$ show-duplicate-java-classes -c target/war/WEB-INF/classes target/war/WEB-INF/lib
...

Android开发场景使用说明

Android开发,有重复类在编译打包时会报[Dex Loader] Unable to execute dex: Multiple dex files define Lorg/foo/xxx/Yyy

但只会给出一个重复类名,如果重复类比较多时,上面打包/报错/排查会要进行多次,而Android的打包比较费时,这个过程比较麻烦,希望可以一次把所有重复类都列出来,一起排查掉。

Gradle作为构建工程示意过程。

Appbuild.gradle中添加拷贝库到目录build/dependencies下。

task copyDependencies(type: Copy) {
    def dest = new File(buildDir, "dependencies")

    // clean dir
    dest.deleteDir()
    dest.mkdirs()

    // fill dir with dependencies
    from configurations.compile into dest
}
# 拷贝依赖
$ ./gradlew app:copyDependencies
...
# 检查重复类
$ show-duplicate-java-classes app/build/dependencies
...

示例

$ show-duplicate-java-classes WEB-INF/lib
COOL! No duplicate classes found!

================================================================================
class paths to find:
================================================================================
1  : WEB-INF/lib/sourceforge.spring.modules.context-2.5.6.SEC02.jar
2  : WEB-INF/lib/misc.htmlparser-0.0.0.jar
3  : WEB-INF/lib/normandy.client-1.0.2.jar
...

$ show-duplicate-java-classes -c WEB-INF/classes WEB-INF/lib
Found duplicate classes in below class path:
1  (293@2): WEB-INF/lib/sourceforge.spring-2.5.6.SEC02.jar WEB-INF/lib/sourceforge.spring.modules.orm-2.5.6.SEC02.jar
2  (2@3): WEB-INF/lib/servlet-api-3.0-alpha-1.jar WEB-INF/lib/jsp-api-2.1-rev-1.jar WEB-INF/lib/jstl-api-1.2-rev-1.jar
3  (104@2): WEB-INF/lib/commons-io-2.2.jar WEB-INF/lib/jakarta.commons.io-2.0.jar
4  (6@3): WEB-INF/lib/jakarta.commons.logging-1.1.jar WEB-INF/lib/commons-logging-1.1.1.jar WEB-INF/lib/org.slf4j.jcl104-over-slf4j-1.5.6.jar
5  (344@2): WEB-INF/lib/sourceforge.spring-2.5.6.SEC02.jar WEB-INF/lib/sourceforge.spring.modules.context-2.5.6.SEC02.jar
...

================================================================================
Duplicate classes detail info:
================================================================================
1  (293@2): WEB-INF/lib/sourceforge.spring-2.5.6.SEC02.jar WEB-INF/lib/sourceforge.spring.modules.orm-2.5.6.SEC02.jar
    1   org/springframework/orm/toplink/TopLinkTemplate$13.class
    2   org/springframework/orm/hibernate3/HibernateTemplate$24.class
    3   org/springframework/orm/jpa/vendor/HibernateJpaDialect.class
    4   org/springframework/orm/hibernate3/TypeDefinitionBean.class
    5   org/springframework/orm/hibernate3/SessionHolder.class
    ...
2  (2@3): WEB-INF/lib/servlet-api-3.0-alpha-1.jar WEB-INF/lib/jsp-api-2.1-rev-1.jar WEB-INF/lib/jstl-api-1.2-rev-1.jar
    1   javax/servlet/ServletException.class
    2   javax/servlet/ServletContext.class
3  (104@2): WEB-INF/lib/commons-io-2.2.jar WEB-INF/lib/jakarta.commons.io-2.0.jar
    1   org/apache/commons/io/input/ProxyReader.class
    2   org/apache/commons/io/output/FileWriterWithEncoding.class
    3   org/apache/commons/io/output/TaggedOutputStream.class
    4   org/apache/commons/io/filefilter/NotFileFilter.class
    5   org/apache/commons/io/filefilter/TrueFileFilter.class
    ...
...

================================================================================
class paths to find:
================================================================================
1  : WEB-INF/lib/sourceforge.spring.modules.context-2.5.6.SEC02.jar
2  : WEB-INF/lib/misc.htmlparser-0.0.0.jar
3  : WEB-INF/lib/normandy.client-1.0.2.jar
4  : WEB-INF/lib/xml.xmlgraphics__batik-css-1.7.jar-1.7.jar
5  : WEB-INF/lib/jakarta.ecs-1.4.2.jar
...

贡献者

tgic提供此脚本。友情贡献者的链接commandlinefu.cn|微博linux命令行精选

在当前目录下所有jar文件里,查找类或资源文件。 支持LinuxMacWindowscygwinMSSYS

用法

# 在当前目录下所有`jar`文件里,查找类或资源文件。
find-in-jars 'log4j\.properties'
find-in-jars 'log4j\.xml$'
find-in-jars log4j\\.xml$ # 和上面命令一样,Shell转义的不同写法而已
find-in-jars 'log4j(\.properties|\.xml)$'

# -d选项 指定 查找目录(覆盖缺省的当前目录)
find-in-jars 'log4j\.properties$' -d /path/to/find/directory
# 支持多个查找目录
find-in-jars 'log4j\.properties' -d /path/to/find/directory1 -d /path/to/find/directory2

# 帮助信息
$ find-in-jars -h
Usage: find-in-jars [OPTION]... PATTERN
Find file in the jar files under specified directory(recursive, include subdirectory).
The pattern default is *extended* regex.

Example:
    find-in-jars.sh 'log4j\.properties'
    find-in-jars.sh '^log4j(\.properties|\.xml)$' # search file log4j.properties/log4j.xml at zip root
    find-in-jars.sh 'log4j\.properties$' -d /path/to/find/directory
    find-in-jars.sh 'log4j\.properties' -d /path/to/find/dir1 -d /path/to/find/dir2

Options:
  -d, --dir              the directory that find jar files, default is current directory.
                         this option can specify multiply times to find in multiply directory.
  -E, --extended-regexp  PATTERN is an extended regular expression (*default*)
  -F, --fixed-strings    PATTERN is a set of newline-separated strings
  -G, --basic-regexp     PATTERN is a basic regular expression
  -P, --perl-regexp      PATTERN is a Perl regular expression
  -h, --help             display this help and exit

注意,Pattern缺省是grep扩展正则表达式。

示例

# 在当前目录下的所有Jar文件中,查找出 log4j.properties文件
$ find-in-jars 'log4j\.properties$'
./hadoop-core-0.20.2-cdh3u3.jar!log4j.properties

# 查找出 以Service结尾的类
$ ./find-in-jars 'Service.class$'
./WEB-INF/libs/spring-2.5.6.SEC03.jar!org/springframework/stereotype/Service.class
./rpc-benchmark-0.0.1-SNAPSHOT.jar!com/taobao/rpc/benchmark/service/HelloService.class
......

# 在指定的多个目录的Jar文件中,查找出 properties文件
$ find-in-jars '\.properties$' -d ../WEB-INF/lib -d ../deploy/lib | grep -v '/pom\.properties$'
../WEB-INF/lib/aspectjtools-1.6.2.jar!org/aspectj/ajdt/ajc/messages.properties
../WEB-INF/lib/aspectjtools-1.6.2.jar!org/aspectj/ajdt/internal/compiler/parser/readableNames.properties
../WEB-INF/lib/aspectjweaver-1.8.8.jar!org/aspectj/weaver/XlintDefault.properties
../WEB-INF/lib/aspectjweaver-1.8.8.jar!org/aspectj/weaver/weaver-messages.properties
../deploy/lib/groovy-all-1.1-rc-1.jar!groovy/ui/InteractiveShell.properties
../deploy/lib/groovy-all-1.1-rc-1.jar!org/codehaus/groovy/tools/shell/CommandAlias.properties
../deploy/lib/httpcore-4.3.3.jar!org/apache/http/version.properties
../deploy/lib/httpmime-4.2.2.jar!org/apache/http/entity/mime/version.properties
../deploy/lib/javax.servlet-api-3.0.1.jar!javax/servlet/LocalStrings_fr.properties
../deploy/lib/javax.servlet-api-3.0.1.jar!javax/servlet/http/LocalStrings_es.properties
......

参考资料

在多个Jar(Zip)文件查找Log4J配置文件的Shell命令行