# JDK 工具[^1] 在 JDK 的 bin 目录下,为 JAVA 开发人员提供了许多工具,从使用方式来说,可以分为两种: - 直接执行这些程序,比如 jstack、jmap 等,这种方式需要记住不同命令的使用方式,个人更偏向使用 jcmd,但这种方式输出的信息更全面。 - 使用`jcmd `的方式,这种方式可以利用 `jcmd help` 查看支持的子命令,使用方式更统一、简单。 ![alt text](./img/image-7.png) ## 编译 `javac` 指定目录编译: ```bash ➜ javac greetings/*.java ➜ ls greetings Aloha.class GutenTag.class Hello.class Hi.class Aloha.java GutenTag.java Hello.java Hi.java ``` 指定编译时的类路径,用于查找依赖的类和包编译: ```bash ➜ javac -classpath /examples:/lib/Banners.jar /examples/greetings/Hi.java ``` ## 反编译 `javap` 反编译 Java 字节码文件(.class 文件),将其转换成可读的 Java 代码。 下面是一些常用的 javap 命令选项: -verbose:输出详细的反编译信息,包括类名、父类、接口、字段、方法等。 -classpath :指定类路径,用于查找依赖的类和包。 -l:输出行号和本地变量表。 -s:输出源代码。 -c:输出字节码指令。 ```bash ➜ javap -v Hello Warning: File ./Hello.class does not contain class Hello Classfile /home/lighk/IdeaProjects/digital-energy/src/test/java/com/custom/de/Hello.class Last modified Jun 24, 2024; size 354 bytes SHA-256 checksum 3d1583ca57fed9acfeaefb2f97d657d61108c0eafe342b593bee7d3a2d9d1786 Compiled from "Hello.java" public class com.custom.de.Hello minor version: 0 major version: 66 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #10 // com/custom/de/Hello super_class: #2 // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 1 Constant pool: #1 = Methodref #2.#3 // java/lang/Object."":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "":()V #4 = Utf8 java/lang/Object #5 = Utf8 #6 = Utf8 ()V #7 = String #8 // zhangSan #8 = Utf8 zhangSan #9 = Fieldref #10.#11 // com/custom/de/Hello.name:Ljava/lang/String; #10 = Class #12 // com/custom/de/Hello #11 = NameAndType #13:#14 // name:Ljava/lang/String; #12 = Utf8 com/custom/de/Hello #13 = Utf8 name #14 = Utf8 Ljava/lang/String; #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 getName #18 = Utf8 ()Ljava/lang/String; #19 = Utf8 SourceFile #20 = Utf8 Hello.java { public com.custom.de.Hello(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: ldc #7 // String zhangSan 7: putfield #9 // Field name:Ljava/lang/String; 10: return LineNumberTable: line 3: 0 line 4: 4 public java.lang.String getName(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #9 // Field name:Ljava/lang/String; 4: areturn LineNumberTable: line 7: 0 } SourceFile: "Hello.java" ``` ## 进程启动 java 命令用于运行Java程序(执行Java类的main方法,目标文件是字节码文件,即javac编译后的领土 `*.class` 文件),使用示例如下: ```bash ➜ java -classpath lib/*:. com.example.HelloWorld ``` 常用的 java 命令参数: - ` [args...]`:运行指定的 Java 类,例如 `java com.example.HelloWorld` 表示运行 `com.example.HelloWorld` 类的 `main` 方法,并传递空的命令行参数。 - `-classpath `:指定类路径,用于查找依赖的类和包。 - `-Xmx`:设置 Java 堆内存的最大值,例如 `-Xmx512m` 表示最大堆内存为 512 MB。 - `-Xms`:设置 Java 堆内存的初始值,例如 `-Xms256m` 表示初始堆内存为 256 MB。 - `-D=`:设置系统属性,例如 -Dfile.encoding=UTF-8 表示设置文件编码为 UTF-8。 - `-jar [args...]`:运行 JAR 文件,例如 `java -jar myapp.jar` 表示运行 myapp.jar 文件,并传递空的命令行参数。 - `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000`:开启端口为 8000 的远程调试模式 ```{note} 当执行 `java -jar ` 命令时发生了什么? 实际上 `java` 命令会按照以下步骤执行: 1. 查找并加载 JAR 文件中的 `META-INF/MANIFEST.MF` 文件。 2. 在 `MANIFEST.MF` 文件中查找 `Main-Class` 属性指定的主类。 3. 加载并执行主类的 `main` 方法。 ``` ## 远程调试 **服务端配置**: - jdk 1.4.x: `-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000` - jdk 5-8: `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000` - jdk 9-*: `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000` ```{note} `-agentlib:jdwp` 和 `-Xdebug -Xrunjdwp` 有什么区别呢?[^2] `-Xdebug -Xrunjdwp` 对后续的版本依然生效,但是以解释模式运行,没有 JIT,所以运行速度会慢一些。因此对于 jdk5 之后的版本,都建议使用 `-agentlib:jdwp`。 ``` **客户端配置**: ![alt text](./img/image-6.png) ## JVM 参数 ### 堆大小 - `-Xmx`:设置 JVM 最大堆内存大小,例如 -Xmx2g 表示最大堆内存为 2GB。 - `-Xms`:设置 JVM 初始堆内存大小,例如 -Xms1g 表示初始堆内存为 1GB。 ### GC 日志[^3] ```bash # 必备 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintGCApplicationStoppedTime # 可选 -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 # GC日志输出的文件路径 -Xloggc:/path/to/gc-%t.log # 开启日志文件分割 -XX:+UseGCLogFileRotation # 最多分割几个文件,超过之后从头文件开始写 -XX:NumberOfGCLogFiles=14 # 每个文件上限大小,超过就触发分割 -XX:GCLogFileSize=100M ``` ### OOM 堆转储 ```bash # 在 JVM 抛出 OOM 异常时自动生成堆转储文件。 -XX:+HeapDumpOnOutOfMemoryError # 指定堆转储文件的保存路径 -XX:HeapDumpPath=` ``` ```{Attention} - **目录不存在** 转储文件路径种的有不存在的目录,比如 `-XX:HeapDumpPath=./tmp/`,但是`tmp`不存在,就不会生成转储文件; - **指定文件名** 如果指定了转储存文件名,比如 `-XX:HeapDumpPath=./tmp/dump.hprof`, 那生成的文件名一定是`dump.hprof`,就可能会发生文件覆盖,所以不建议指定文件名,这样自动生成的文件名就这种形式`java_pid1861234.hprof`。 ``` ### 其他 - `-XX:+UseParallelGC`:使用并行垃圾回收器。 - `-XX:+UseConcMarkSweepGC`:使用 CMS 垃圾回收器。 - `-XX:+UseG1GC`:使用 G1 垃圾回收器。 - `-XX:MaxPermSize=`:设置永久代的最大大小。 - `-XX:MaxMetaspaceSize=`:设置元空间的最大大小。 - `-XX:NewSize=`:设置新生代的初始大小。 - `-XX:MaxNewSize=`:设置新生代的最大大小。 - `-XX:SurvivorRatio=`:设置 Eden 区和 Survivor 区的比例。 - `-XX:ThreadStackSize=`:设置线程栈的大小。 ```{note} JVM 参数很多,总体上可以分成三类:[^4] - **`-`**:标准参数,比如 `-verbose:`gc` 这类表示标准实现,所有的虚拟机都需要实现这些参数的功能,且向后兼容; - **`-X`**:非标准参数,默认 JVM 会实现这些参数的功能,但是不保证所有的 JVM 实现都满足,且不保证向后兼容; - **`-XX`**:非 Stable 参数,这些参数在不同的 JVM 上会有不同的实现,这些参数不推荐在生成环境中使用,以后很有可能会被取消,需要慎重使用; 关于JVM选项的几点: - 布尔型参数:**`-XX:+`** 表示打开, **`-XX:-`** 表示关闭。(比如`-XX:+PrintGCDetails`); - 数字型参数:通过 **`-XX:=`** 设定。数字可以是m/M(兆字节),k/K(千字节),g/G(G字节)。比如:32K表示32768字节; - 字符行参数:通过 **`-XX:=`** 设定,通常用来指定一个文件,路径,或者一个命令列表。(比如`-XX:HeapDumpPath=./java_pid.hprof`) ``` ## 进程查看 ### `jps` ```sh ➜ ~ jps 17242 Jps 16494 iot-hub-1.0.0-SNAPSHOT.jar ``` ### `jcmd` ```sh ➜ ~ jcmd 17296 sun.tools.jcmd.JCmd 16494 /usr/local/jdk/app/iot-hub_web/iot-hub-1.0.0-SNAPSHOT.jar --spring.config.location=file:/usr/local/jdk/app/iot-hub_web/application-public.properties,file:/usr/local/jdk/app/iot-hub_web/application-dev.properties [root@dev ~]# ``` ### `ps` 这个其是是操作系统提供的命令,可以查看更多的信息 ```bash ➜ ~ ps -aux | grep java root 22593 0.1 6.8 7991480 1107164 ? Sl 4月30 140:58 /usr/local/webserver/jdk/bin/java -Xms2g -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:HeapDumpPath=/usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../logs -Dlog.path=/usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../logs -Duser.dir=/usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../ -Dserver.port=9527 -Ddata.path=/usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../data -Dmail.username=datax -Dmail.password=123456 -Dlogging.config=/usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../conf/logback.xml -classpath /usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../lib/*:/usr/local/jdk/app/datax-web-2.1.2/modules/datax-admin/bin/../conf:. com.wugui.datax.admin.DataXAdminApplication ``` ## 线程查看 这里通过操作系统`top`命令查看`java`进程的线程: 1. 通过`jcmd`拿到进程`pid` 2. 通过`top -H -p `,主要看哪些线程占 CPU 较高 3. 使用`jstack ` 查看具体的线程栈 ## 线程快照 - `jstack ` - `jcmd Thread.print` ```bash ➜ ~ jstack help Usage: jstack [-l] (to connect to running process) jstack -F [-m] [-l] (to connect to a hung process) jstack [-m] [-l] (to connect to a core file) jstack [-m] [-l] [server_id@] (to connect to a remote debug server) Options: -F to force a thread dump. Use when jstack does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -h or -help to print this help message ``` ```{note} [jcmd](#jcmd-诊断) 的参数无须额外记忆(比如这里使用的`Thread.print`),直接使用`jcmd help` 就可以查看当前 java 进程支持哪些参数 ``` ## 内存快照 - `jmap -dump:format=b,file=heap.bin ` - `jcmd GC.heap_dump ` 然后可以使用如下工具分析: - [Eclipse Memory Analyzer(MAT)](https://eclipse.dev/mat/) - [JDK VisualVM](https://visualvm.github.io/) - [YourKit Java Profiler](https://www.ej-technologies.com/products/jprofiler/overview.html) ```{caution} **`jmap -dump:live`** `live`选项用于导出 Java 堆中所有存活的对象信息,执行 `jmap -dump:live,format=b,file= ` 命令时,需要注意的是,`-dump:live` 选项会在目标 Java 进程中触发一次 Full GC,因此可能会对应用程序的性能产生影响。 ``` ```bash ➜ ~ jmap -help Usage: jmap [option] (to connect to running process) jmap [option] (to connect to a core file) jmap [option] [server_id@] (to connect to remote debug server) where