【问题标题】:Is there a way to count the IO activity (file access, bytes read)?有没有办法计算 IO 活动(文件访问、读取的字节数)?
【发布时间】:2020-09-03 13:57:42
【问题描述】:

我希望能够了解正在运行的 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);

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

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

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

我做错了什么?

【问题讨论】:

    标签: java io java-io


    【解决方案1】:

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

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

    要走 Java 路线,您可以通过扩展 java.lang.SecurityManager 来实现自己的安全管理器。此类提供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)
    

    跟踪实现有效。我们甚至可以看到类加载尝试。但是,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());
    

    在这种特殊情况下,不需要自定义策略,因为默认策略未激活。

    使用安全管理器来跟踪文件系统访问肯定不是最佳选择,因为缺少可能与您的调试方案相关的细节,但如果您没有其他选择并且需要完成工作,这是一个很好的折衷方案。

    【讨论】:

    • 我添加了有关如何扩展默认安全策略的更多信息,以便安装安全管理器不会限制所需的功能。
    • 谢谢你,做到了。最后一个问题,你知道为什么每个 .class 文件都会被加载两次吗?
    • 并不是VM读取类两次。各种 I/O 操作触发安全管理器。在我的示例中,一个类读取会产生三个由statopenstat 引起的checkRead 调用。单个读取不会调用checkRead,只有创建读取文件描述符才会调用。
    • 这完全取决于实现。在 Oracle JVM 的情况下,访问检查似乎不会在每次读取的基础上进行。当然,如果要分析的代码打开、读取和关闭,这将对应于一次读取。在这种情况下,并且在监视文件访问并强调能够识别读取文件的情况下,这可能非常有用。对于识别打开文件的读取,它不是。在后一种情况下,最好使用 strace 和进程监视器。检测字节码是另一种选择,但这不包括 JNI。
    • 在 Image IO 的情况下,它可能很有用,因为实现使用临时文件作为后备存储,具体取决于操作。如果这些访问是基于 Java 的,您会看到这些访问。
    猜你喜欢
    • 2012-12-10
    • 2011-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-25
    相关资源
    最近更新 更多