Category: java

  • 不同实现递归遍历文件夹的性能差异

    背景

    公司的存储项目主要代码都是java实现, 使用引用计数判断一个数据块是否要被回收.
    当数据块的引用计数变为0时, 会触发回收这个数据块, 把它移动到trash 文件夹.
    回收数据块前需要删除重删库中数据块的指纹, 这个过程中, 有可能有新的数据引用到这些数据块.
    为了解决这个问题,又减少对性能影响. 规定对如果数据块对应的数据指纹最近被查询, 那么这个数据块不能被回收.
    这导致了存储长期运行, 会有一些数据块已经没用但是没有被回收. 为此需要定期扫描全部的数据块, 尝试对所有数据块进行一次回收,从而回收那些无用的数据块.

    因此存储开发了一个遍历文件夹过程找出数据块的功能.使用java 的Files.walk 接口实现的.

    在客户环境实际使用这个功能时, 发现远比直接使用linux find 命令慢. 使用存储的遍历功能执行了5个小时没有结束, 而使用find命令一个小时可以执行完.

    测试环境

    调研了一下, 类似linux find的工具, 有一个bfs工具
    在测试环境对同一个文件夹进行遍历, 过滤出数据块.

    存储功能花费时间: 1161秒
    find: 114秒
    bfs:  63秒
    

    发现, java的Files.walk 遍历找数据块的性能只有linux find 的十分之一. 然后bfs比find的性能快了接近一倍.

    bfs 作者写了篇文章讲述bfs的实现原理. 主要是使用了linux readdir 接口.

    实现不同遍历文件夹的接口, 测试性能

    为此我写了一个程序, 使用java 实现调用不同接口遍历文件夹, 测试它们的性能差异. github地址: https://github.com/IUHHUI/benmark-traversal-dir

    0       JAVA_LIST_FILES              使用java File listFiles接口 实现
    1       JAVA_FILES_WALk              使用java Files.walk接口 实现
    2       JAVA_FILES_PARALLEL          使用java Files.walk接口+stream并行 实现
    3       AWS_CRT                      使用aws crt 库的DirectoryTraversal.traverse 实现
    4       JNR_READ_DIR                 使用java jnr调用linux readdir接口 实现
    5       JNR_READ_DIR_FORK_JOIN       使用java jnr调用linux readdir接口+java ForkJoin框架 实现
    6       JNR_TRAVERSAL_SO_SINGLE      使用java jnr调用c语言写的遍历文件夹so 实现
    7       JNR_TRAVERSAL_SO_PARALLEL    使用java jnr调用c语言写的多线程遍历文件夹so 实现
    

    测试脚本:

    for i in $(seq 0 7) ; do
            echo 3 > /proc/sys/vm/drop_caches
            sync
            sleep 2
            rm -f  "/meta/data${i}";
            java -jar benmark-traversal-dir-1.0-SNAPSHOT-jar-with-dependencies.jar "${i}" /data/ "/meta/data${i}";
    done
    

    测试结果如下:

    JAVA_LIST_FILES Time elapsed: 1217766ms. result : SUCCESS
    JAVA_FILES_WALk Time elapsed: 1233453ms. result : SUCCESS
    JAVA_FILES_PARALLEL Time elapsed: 1165995ms. result : SUCCESS
    AWS_CRT Time elapsed: 1403918ms. result : SUCCESS
    JNR_READ_DIR Time elapsed: 100373ms. result : SUCCESS
    JNR_READ_DIR_FORK_JOIN Time elapsed: 36833ms. result : SUCCESS
    JNR_TRAVERSAL_SO_SINGLE Time elapsed: 113996ms. result : SUCCESS
    JNR_TRAVERSAL_SO_PARALLEL Time elapsed: 38974ms. result : SUCCESS
    

    测试发现 JNR_READ_DIR_FORK_JOIN 的性能是 使用java 原生接口 的40倍.

    火焰图分析

    使用 阿里的arthas 生成火焰图, 分析 java 原生接口到底慢在哪里.

    使用java原生接口java/io/File.ListFiles时, 需要使用java/io/File.isFile判断是否文件, 从火焰图看到基本都在 java/io/File.isFile 上,而这个函数底层基本都是在调用vfs_statfs.
    使用java原生接口Files.walk时, 从火焰图看到基本都在 java/nio/file/FileTreeWalker.visit 上, 而这个函数底层基本都是在调用vfs_statfs.
    而使用linux readdir, 可以避免查询每一个文件的属性.

  • 通过arthas 打印spring项目的配置

    spring程序运行起来后, 有时候想看看配置是否正确或者spring项目的一些默认配置项是什么.可以通过阿里云的arthas 将内存中的spring 配置类打印出来.

    查找实例的类加载器

    [arthas@439]$ sc -d org.springframework.boot.autoconfigure.web.ServerProperties
     class-info        org.springframework.boot.autoconfigure.web.ServerProperties
     code-source       /usr/share/octopus/lib/spring-boot-autoconfigure-3.2.1.jar
     name              org.springframework.boot.autoconfigure.web.ServerProperties
     isInterface       false
     isAnnotation      false
     isEnum            false
     isAnonymousClass  false
     isArray           false
     isLocalClass      false
     isMemberClass     false
     isPrimitive       false
     isSynthetic       false
     simple-name       ServerProperties
     modifier          public
     annotation        org.springframework.boot.context.properties.ConfigurationProperties
     interfaces
     super-class       +-java.lang.Object
     class-loader      +-jdk.internal.loader.ClassLoaders$AppClassLoader@5ffd2b27
                         +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@34cd072c
     classLoaderHash   5ffd2b27
    

    打印类情况

    [arthas@439]$ vmtool -c  5ffd2b27 --action getInstances  --className org.springframework.boot.autoconfigure.web.ServerProperties  -x 2
    @ServerProperties[][
        @ServerProperties[
            port=@Integer[19091],
            address=null,
            error=@ErrorProperties[org.springframework.boot.autoconfigure.web.ErrorProperties@2b101c68],
            forwardHeadersStrategy=null,
            serverHeader=null,
            maxHttpRequestHeaderSize=@DataSize[8192B],
            shutdown=@Shutdown[IMMEDIATE],
            ssl=@Ssl[org.springframework.boot.web.server.Ssl@4d349bbc],
            compression=@Compression[org.springframework.boot.web.server.Compression@195400d1],
            http2=@Http2[org.springframework.boot.web.server.Http2@1654070d],
            servlet=@Servlet[org.springframework.boot.autoconfigure.web.ServerProperties$Servlet@3d8cbaf3],
            reactive=@Reactive[org.springframework.boot.autoconfigure.web.ServerProperties$Reactive@548c3f85],
            tomcat=@Tomcat[org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat@6d67ef4d],
            jetty=@Jetty[org.springframework.boot.autoconfigure.web.ServerProperties$Jetty@5b3df183],
            netty=@Netty[org.springframework.boot.autoconfigure.web.ServerProperties$Netty@3be9335],
            undertow=@Undertow[org.springframework.boot.autoconfigure.web.ServerProperties$Undertow@36fbf4d0],
        ],
    ]
    
  • arthas常用的命令

    查询静态变量

    [arthas@174072]$ getstatic com.mulang.octopus.Main standAloneStorageNode
    field: standAloneStorageNode
    @Boolean[true]
    Affect(row-cnt:1) cost in 6 ms.
    

    使用ognl方式

    [arthas@227877]$  ognl "@java.nio.Bits@RESERVED_MEMORY"
    @AtomicLong[
        serialVersionUID=@Long[1927816293512124184],
        VM_SUPPORTS_LONG_CAS=@Boolean[true],
        U=@Unsafe[jdk.internal.misc.Unsafe@12b9a7fa],
        VALUE=@Long[16],
        value=@Long[9515349],
        serialVersionUID=@Long[-8742448824652078965],
    ]
    [arthas@227877]$
    [arthas@227877]$  ognl "@java.nio.Bits@RESERVED_MEMORY.get()"
    @Long[9515349]
    [arthas@227877]$
    

    调用没有参数的静态命令.

    #public static DataStoreType typeDataStore()
    
    [arthas@174072]$ ognl '@com.mulang.octopus.blockstore.BlockServiceProxy@typeDataStore()'
    @DataStoreType[LOCAL_DISK]
    

    调用有参数的静态命令.

    #public static long diskFreeCapacity(int serverId, int diskId) throws IOException
    
    [arthas@11123]$ ognl  '@com.mulang.octopus.blockstore.BlockServiceProxy@diskFreeCapacity(3,40001)'
    @Long[211085709312]
    

    解决方法重载

    通过制定参数个数的形式解决不同的方法签名

    tt -t *Test print params.length\=\=1
    

    通过制定参数个数的形式解决不同的方法签名,如果参数个数一样,你还可以这样写

    tt -t *Test print ‘params[1] instanceof Integer’

    解决指定参数

    tt -t *Test print params[0].mobile\=\=”13989838402″

    构成条件表达式的 Advice 对象

    表达式核心变量

    public class Advice {
    
        private final ClassLoader loader;   //本次调用类所在的 ClassLoader
        private final Class<?> clazz;       //本次调用类的 Class 引用
        private final ArthasMethod method;  //本次调用方法反射引用
        private final Object target;        //本次调用类的实例
        //本次调用参数列表,这是一个数组,如果方法是无参方法则为空数组
        private final Object[] params;
        //本次调用返回的对象。当且仅当 isReturn==true 成立时候有效,表明方法调用是以正常返回的方式结束。如果当前方法无返回值 void,则值为 null
        private final Object returnObj;
        //本次调用抛出的异常。当且仅当 isThrow==true 成立时有效,表明方法调用是以抛出异常的方式结束。
        private final Throwable throwExp;
        //辅助判断标记,当前的通知节点有可能是在方法一开始就通知,此时 isBefore==true 成立,同时 isThrow==false 和 isReturn==false,因为在方法刚开始时,还无法确定方法调用将会如何结束。
        private final boolean isBefore;
        //辅助判断标记,当前的方法调用以抛异常的形式结束。
        private final boolean isThrow;
        //辅助判断标记,当前的方法调用以正常返回的形式结束。
        private final boolean isReturn;
    
        // getter/setter
    }
    

    所有变量都可以在表达式中直接使用,如果在表达式中编写了不符合 OGNL 脚本语法或者引入了不在表格中的变量,则退出命令的执行;

    热更新某个类

    class/classloader 相关

    • classloader – 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource
    • dump – dump 已加载类的 byte code 到特定目录
    • jad – 反编译指定已加载类的源码
    • mc – 内存编译器,内存编译.java文件为.class文件
    • redefine – 加载外部的.class文件,redefine 到 JVM 里
    • retransform – 加载外部的.class文件,retransform 到 JVM 里
    • sc – 查看 JVM 已加载的类信息
    • sm – 查看已加载类的方法信息
    $ retransform /tmp/MathGame.class
    retransform success, size: 1, classes:
    demo.MathGame
    
    

    Note:

    • redefine 的 class 不能修改、添加、删除类的 field 和 method,包括方法参数、方法名称及返回值
    • 如果 mc 失败,可以在本地开发环境编译好 class 文件,上传到目标系统,使用 redefine 热加载 class

    tt 和 watch

    tt

    • 需要强调的是,tt 命令是将当前环境的对象引用保存起来,但仅仅也只能保存一个引用而已。如果方法内部对入参进行了变更,或者返回的对象经过了后续的处理,那么在 tt 查看的时候将无法看到当时最准确 的值。这也是为什么 watch 命令存在的意义
    表格字段 字段解释
    INDEX 时间片段记录编号,每一个编号代表着一次调用,后续 tt 还有很多命令都是基于此编号指定记录操作,非常重要。
    TIMESTAMP 方法执行的本机时间,记录了这个时间片段所发生的本机时间
    COST(ms) 方法执行的耗时
    IS-RET 方法是否以正常返回的形式结束
    IS-EXP 方法是否以抛异常的形式结束
    OBJECT 执行对象的hashCode(),注意,曾经有人误认为是对象在 JVM 中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体
    CLASS 执行的类名
    METHOD 执行的方法名
    [arthas@15217]$ tt -t demo.MathGame primeFactors -n 3
    Press Q or Ctrl+C to abort.
    Affect(class count: 1 , method count: 1) cost in 100 ms, listenerId: 1
     INDEX       TIMESTAMP                    COST(ms)      IS-RET      IS-EXP      OBJECT                CLASS                                      METHOD
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     1000        2023-03-10 03:37:42          1.419275      true        false       0x232204a1            MathGame                                   primeFactors
     1001        2023-03-10 03:37:43          0.144813      false       true        0x232204a1            MathGame                                   primeFactors
     1002        2023-03-10 03:37:44          4.481957      true        false       0x232204a1            MathGame                                   primeFactors
    Command execution times exceed limit: 3, so command will exit. You can set it with -n option.
    [arthas@15217]$ tt -t demo.MathGame primeFactors -n 2
    Press Q or Ctrl+C to abort.
    Affect(class count: 1 , method count: 1) cost in 28 ms, listenerId: 2
     INDEX       TIMESTAMP                    COST(ms)      IS-RET      IS-EXP      OBJECT                CLASS                                      METHOD
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     1003        2023-03-10 03:38:15          0.128059      true        false       0x232204a1            MathGame                                   primeFactors
     1004        2023-03-10 03:38:16          0.028727      true        false       0x232204a1            MathGame                                   primeFactors
    Command execution times exceed limit: 2, so command will exit. You can set it with -n option.
    
    #list
    [arthas@15217]$ tt -l
     INDEX       TIMESTAMP                    COST(ms)      IS-RET      IS-EXP      OBJECT                CLASS                                      METHOD
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     1000        2023-03-10 03:37:42          1.419275      true        false       0x232204a1            MathGame                                   primeFactors
     1001        2023-03-10 03:37:43          0.144813      false       true        0x232204a1            MathGame                                   primeFactors
     1002        2023-03-10 03:37:44          4.481957      true        false       0x232204a1            MathGame                                   primeFactors
     1003        2023-03-10 03:38:15          0.128059      true        false       0x232204a1            MathGame                                   primeFactors
     1004        2023-03-10 03:38:16          0.028727      true        false       0x232204a1            MathGame                                   primeFactors
    Affect(row-cnt:5) cost in 3 ms.
    [arthas@15217]$ tt -i 1003
     INDEX          1003
     GMT-CREATE     2023-03-10 03:38:15
     COST(ms)       0.128059
     OBJECT         0x232204a1
     CLASS          demo.MathGame
     METHOD         primeFactors
     IS-RETURN      true
     IS-EXCEPTION   false
     PARAMETERS[0]  @Integer[162701]
     RETURN-OBJ     @ArrayList[
                        @Integer[7],
                        @Integer[11],
                        @Integer[2113],
                    ]
    Affect(row-cnt:1) cost in 4 ms.
    
    #replay
    [arthas@15217]$ tt -i 1003 -p
     RE-INDEX       1003
     GMT-REPLAY     2023-03-10 04:37:14
     OBJECT         0x232204a1
     CLASS          demo.MathGame
     METHOD         primeFactors
     PARAMETERS[0]  @Integer[162701]
     IS-RETURN      true
     IS-EXCEPTION   false
     COST(ms)       0.047237
     RETURN-OBJ     @ArrayList[
                        @Integer[7],
                        @Integer[11],
                        @Integer[2113],
                    ]
    Time fragment[1003] successfully replayed 1 times.
    
  • openstack4j 开启debug模式

    // 开启debug 模式.
    OSFactory.enableHttpLoggingFilter(true);
    // V3 authentication
    OSClient.OSClientV3 os = OSFactory.builderV3().endpoint(iamDomainV3)
                    .credentials(this.getUserName(), this.getPassWord(), Identifier.byName(this.domainName))
                    .scopeToDomain(Identifier.byName(this.domainName)).perspective(Facing.PUBLIC).withConfig(config)
                    .authenticate();
    

    openstack4j 支持输出http 日志. 方便查看请求发送的参数.