bigywater.tech
Java · 268

jdeps:从字节码分析模块和 API 依赖

jdeps 从字节码里分析一个 jar 依赖了哪些 JDK 模块、哪些 JDK 内部 API、哪些其它 jar。这篇用三个真实的开源库逐个运行,并用一个字符串反射的例子展示 jdeps 的盲区。

jdk.unsupported
netty-common 与 guava 都因 sun.misc.Unsafe 依赖的 JDK 模块
--multi-release 21
jdeps 分析多版本 jar(如 commons-lang3)必须加的选项
反射
jdeps 看不到的依赖:Class.forName 的字符串

--print-module-deps 与 --list-deps

实测 · 本机执行输出
  commons-lang3 是多版本 jar,不加 --multi-release 时:返回码 2 ; Error: commons-lang3-3.17.0.jar is a multi-release jar file but --multi-release option is not set
  netty-common 4.1.115   --print-module-deps → java.base,java.logging,jdk.unsupported
                         --list-deps → java.base java.logging jdk.unsupported;--list-reduced-deps → java.base java.logging jdk.unsupported
  guava 33.4.0           --print-module-deps → java.base,java.logging,jdk.unsupported
                         --list-deps → java.base java.logging jdk.unsupported;--list-reduced-deps → java.base java.logging jdk.unsupported
  commons-lang3 3.17.0   --print-module-deps → java.base,java.desktop
                         --list-deps → java.base java.desktop;--list-reduced-deps → java.base java.desktop

--jdk-internals

实测 · 本机执行输出
  [netty-common] 返回码 0;36 行输出:
     netty-common-4.1.115.Final.jar -> jdk.unsupported
      io.netty.util.internal.CleanerJava9 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      io.netty.util.internal.CleanerJava9$1 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      io.netty.util.internal.CleanerJava9$2 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      io.netty.util.internal.PlatformDependent$Mpsc$1 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      io.netty.util.internal.PlatformDependent0 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
  [guava] 返回码 0;22 行输出:
     guava-33.4.0-jre.jar -> jdk.unsupported
      com.google.common.cache.Striped64 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      com.google.common.cache.Striped64$1 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      com.google.common.cache.Striped64$Cell -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      com.google.common.hash.LittleEndianByteArray$UnsafeByteArray -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
      com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$1 -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
  [commons-lang3] 返回码 0;0 行输出:
  guava 加 -q(不打印替代 API 提示): 14 行

依赖图、递归与缺失依赖

实测 · 本机执行输出
  jdeps -s -cp <guava 的依赖> guava.jar(摘要,jar 之间的依赖):
    guava-33.4.0-jre.jar -> <m2>/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar
    guava-33.4.0-jre.jar -> <m2>/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar
    guava-33.4.0-jre.jar -> <m2>/com/google/j2objc/j2objc-annotations/3.0.0/j2objc-annotations-3.0.0.jar
    guava-33.4.0-jre.jar -> java.base
    guava-33.4.0-jre.jar -> java.logging
    guava-33.4.0-jre.jar -> jdk.unsupported
    guava-33.4.0-jre.jar -> <m2>/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar
  不给 -cp、只分析 guava.jar(缺少 failureaccess 等 jar):
    guava-33.4.0-jre.jar -> java.base
    guava-33.4.0-jre.jar -> java.logging
    guava-33.4.0-jre.jar -> jdk.unsupported
    guava-33.4.0-jre.jar -> not found
  --missing-deps: guava-33.4.0-jre.jar -> not found |  com.google.common.base.Absent -> javax.annotation.CheckForNull not found |  com.google.common.base.AbstractIterator -> javax.annotation.CheckForNull not found |  com.google.common.base.CaseFormat$StringConverter -> javax.annotation.CheckForNull not found |  com.google.common.base.Converter -> com.google.errorprone.annotations.CheckReturnValue not found
  -R(递归分析 classpath 上所有依赖 jar)摘要行数: 11
  -dotoutput 生成的文件: ['summary.dot']
  -verbose:class -e Preconditions:503 行;前 3 行: [' com.google.common.cache.CacheBuilder -> com.google.common.base.Preconditions guava-33.4.0-jre.jar', ' com.google.common.cache.CacheBuilderSpec -> com.google.common.base.Preconditions guava-33.4.0-jre.jar', ' com.google.common.cache.CacheBuilderSpec$AccessDurationParser -> com.google.common.base.Preconditions guava-3']

--generate-module-info 与盲区

实测 · 本机执行输出
  --generate-module-info 对 commons-lang3:返回码 1;生成 [];输出:Error: <m2>/org/apache/commons/commons-lang3/3.17.0/commons-lang3-3.17.0.jar is a modular JAR file that cannot be specified with the --generate-module-info option
  --generate-module-info 对 netty-common:返回码 0;生成 ['io.netty.common/versions/21/module-info.java']
  生成的 module-info.java(前 10 行):
    module io.netty.common {
        requires java.logging;
    
        requires transitive jdk.unsupported;
    
        exports io.netty.util;
        exports io.netty.util.collection;
        exports io.netty.util.concurrent;
        exports io.netty.util.internal;
        exports io.netty.util.internal.logging;
  共 exports 行 13 个;requires 行 2 个
  --generate-module-info 对 guava:返回码 0;生成 ['com.google.common/versions/21/module-info.java']
  jdeps --print-module-deps(应用 + commons-lang3)→ java.base,java.desktop (应用里通过字符串反射用到的 java.logging 与 java.sql 都没有出现;java.desktop 来自 commons-lang3)
  运行时 -verbose:class 里来自 jrt: 的模块(不含 CDS 共享类): {'java.base': 8, 'java.sql': 1} ;来自 CDS 共享归档的类 739 个
  应用输出: ['Hello', '通过反射加载 java.util.logging.Logger 所在模块 java.logging', '通过反射加载 java.sql.Driver 所在模块 java.sql', '引导层里的模块数 62']

分步演示

jdeps
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 的 jdeps;被分析的是 netty-common 4.1.115、guava 33.4.0-jre、commons-lang3 3.17.0
    • 没有验证:jdeps 对 Spring 等大量使用反射与 SPI 的框架的效果、--jdk-internals 提示的替代 API 是否都可用、--api-only、jdeps 与 jlink 结合时漏掉反射模块的补救办法(见 jlink 一篇)、--generate-open-module、生成的 module-info.java 能否在编译与运行时直接使用(这里只看了生成内容)
    京ICP备2026067502号-1京公网安备11010502063487号