有没有办法计算 IO activity(文件访问,读取的字节数)?

Is there a way to count the IO activity (file access, bytes read)?

我希望能够了解 运行 JVM 访问磁盘的频率,如果可能的话,它会访问什么。
例如,我想知道: 通过 ImageIO 加载图像是否真的将它加载到内存中,或者它只是创建一个指向文件的指针并在它真正绘制到屏幕时访问它? 还有其他类似的事情。 有办法吗?

更新,我添加了这个:

public class SecurityManagerExtension extends SecurityManager {


    @Override
    public void checkRead(FileDescriptor fd)
    {
        logger.error("reading file1: {}", fd.toString());
        super.checkRead(fd);
    }

    @Override
    public void checkRead(String file)
    {
        logger.error("reading file2: {}", file);
        super.checkRead(file);
    }

    @Override
    public void checkRead(String file, Object context)
    {
        logger.error("reading file3: {} in context {}", file, context.toString());
        super.checkRead(file, context);
    }
}

我已经设置了安全管理器:

    SecurityManagerExtension secMan = new SecurityManagerExtension();
    java.lang.System.setSecurityManager(secMan);

现在,新问题是: 基本上我加载或加载的每个文件,除了 class 个文件,似乎都会导致这样的 StackTrace:

原因:java.security.AccessControlException:访问被拒绝("java.io.FilePermission" "C:\Program Files\Java\jdk-13.0.1\bin\awt.dll" "read")

如果我注释掉 setSecurityManager() 行,一切都会再次正常运行。 我什至试图让我的用户完全访问 JDK 目录,所以这不是真正的权限问题。事实并非如此,因为如果我回到默认状态,一切都会再次正常运行。

我做错了什么?

要跟踪 Java 应用程序的文件系统访问,本机跟踪工具始终是首选。在 Windows 上,使用 Process Monitor to trace I/O. On Linux, use strace。其他平台提供类似的设施。

通过直接在 Java 中跟踪文件系统访问,您可以解决环境限制。例如,strace 在缺少 CAP_SYS_PTRACE 能力的容器中不可用,并且容器主机并不总是可访问的。

要走 Java 路线,您可以通过扩展 java.lang.SecurityManager 来实现自己的安全管理器。此 class 提供了 checkReadcheckWritecheckDelete 方法,一旦代码尝试相应的访问就会调用这些方法。

示例实现:

public class TraceSecurityManager extends SecurityManager {
  public void checkRead(String file) {
    System.out.println("Read: " + file);
  }

  public void checkRead(String file, Object context) {
    System.out.println("Read: " + file);
  }

  public void checkWrite(String file) {
    System.out.println("Write: " + file);
  }

  public void checkDelete(String file) {
    System.out.println("Delete: " + file);
  }
}

为了测试示例,我们使用 Java 编译器作为我们的测试对象。要启用跟踪安全管理器,我们设置适当的系统 属性 并使用有效的 Java 源文件 Test.java:

执行命令
$ java -Djava.security.manager=TraceSecurityManager com.sun.tools.javac.Main Test.java
Read: /home/user/com/sun/tools/javac/resources/spi/compilerProvider.class
Read: /home/user/com/sun/tools/javac/resources/compiler_en.properties
Read: /home/user/com/sun/tools/javac/resources/compiler_en_US.properties
Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getenv.JDK_JAVAC_OPTIONS")
        at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
        at java.base/java.security.AccessController.checkPermission(AccessController.java:897)
        at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:322)
        at java.base/java.lang.System.getenv(System.java:999)
        at jdk.compiler/com.sun.tools.javac.main.CommandLine.appendParsedEnvVariables(CommandLine.java:252)
        at jdk.compiler/com.sun.tools.javac.main.CommandLine.parse(CommandLine.java:99)
        at jdk.compiler/com.sun.tools.javac.main.CommandLine.parse(CommandLine.java:123)
        at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:215)
        at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:170)
        at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:57)
        at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:43)

跟踪实现有效。我们甚至可以看到 class 次加载尝试。但是,javac 失败,因为缺少权限。原因是,通过系统 属性 安装安全管理器后,默认的 Java 安全策略处于活动状态并且不授予所需的权限。要解决此问题,您可以提供最小的自定义策略,也可以使用空实现覆盖 checkPermission 方法。在这种情况下,我选择了最小策略:

grant {
  permission java.security.AllPermission "", "";
};

有了政策,我们可以重新测试:

$ java -Djava.security.policy=test.policy -Djava.security.manager=TraceSecurityManager com.sun.tools.javac.Main Test.java
Read: /home/user/com/sun/tools/javac/resources/spi/compilerProvider.class
Read: /home/user/com/sun/tools/javac/resources/compiler_en.properties
Read: /home/user/com/sun/tools/javac/resources/compiler_en_US.properties
Read: Test.java
Read: Test.java
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/jfxrt.jar
Read: /home/user/META-INF/services/java.nio.file.spi.FileSystemProvider
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: /usr/lib/jvm/java-11-openjdk-11.0.10.0.9-1.el7_9.x86_64/lib/modules
Read: Test.java
Read: /home/user/Test.java
Read: ./.bash_logout
Read: /home/user/.bash_logout
Read: ./.bash_profile
Read: /home/user/.bash_profile
Read: ./.bashrc
Read: /home/user/.bashrc
[...]
Read: /home/user/com/sun/tools/javac/resources/spi/ctProvider.class
Read: /home/user/com/sun/tools/javac/resources/ct_en.properties
Read: /home/user/com/sun/tools/javac/resources/ct_en_US.properties
Read: /home/user/TraceSecurityManager.class
Read: /home/user/Test.class
Read: /home/user
Write: /home/user/Test.class
Read: Test.java

这一次,我们获得了 javac 的完整文件系统访问跟踪。安全管理器也可以在运行时启用,如果您出于任何原因无法控制 Java 命令行,这将非常有用:

System.setSecurityManager(new TraceSecurityManager());

在这种情况下,自定义策略不是必需的,因为默认策略未激活。

使用安全管理器跟踪文件系统访问当然不是最佳选择,因为缺少可能与您的调试方案相关的详细信息,但如果您别无选择并且需要完成工作,这是一个很好的折衷方案.