【问题标题】:java heap analysis with oql: Count unique strings使用 oql 进行 Java 堆分析:计算唯一字符串
【发布时间】:2012-01-04 17:54:13
【问题描述】:

我正在对现有的 java 软件进行内存分析。 oql 中是否有等效的 sql 'group by' 来查看具有相同值但不同实例的对象的计数。

选择计数(*) 来自 java.lang.String s 按 s.toString() 分组

我想获得一个重复字符串的列表以及重复的数量。这样做的目的是查看大量案例,以便可以使用 String.intern() 对其进行优化。

例子:

"foo"    100
"bar"    99
"lazy fox"    50

等等……

【问题讨论】:

    标签: java performance memory heap-memory


    【解决方案1】:

    以下内容基于 Peter Dolberg 的回答,可在VisualVM OQL 控制台中使用:

    var counts={};
    var alreadyReturned={};
    
    filter(
      sort(
        map(heap.objects("java.lang.String"),
        function(heapString){
          if( ! counts[heapString.toString()]){
            counts[heapString.toString()] = 1;
          } else {
            counts[heapString.toString()] = counts[heapString.toString()] + 1;
          }
          return { string:heapString.toString(), count:counts[heapString.toString()]};
        }), 
        'lhs.count < rhs.count'),
      function(countObject) {
        if( ! alreadyReturned[countObject.string]){
          alreadyReturned[countObject.string] = true;
          return true;
        } else {
          return false;
        }
       }
      );
    

    首先对所有 String 实例使用 map() 调用,并为每个 String 创建或更新 counts 数组中的对象。每个对象都有一个string 和一个count 字段。

    结果数组将为每个 String 实例包含一个条目,每个条目的 count 值比相同 String 的前一个条目大一个。 然后将结果按count 字段排序,结果如下所示:

    {
    count = 1028.0,
    string = *null*
    }
    
    {
    count = 1027.0,
    string = *null*
    }
    
    {
    count = 1026.0,
    string = *null*
    }
    
    ...
    

    (在我的测试中,字符串 "*null*" 是最常见的)。

    最后一步是使用一个函数来过滤它,该函数对每个字符串的第一次出现返回 true。它使用alreadyReturned 数组来跟踪已包含哪些字符串。

    【讨论】:

    • 谢谢,很好地解决了这个问题。 oql 使用起来有点尴尬。这一切都必须在一个函数中发生......
    • 哇,不知道 jvisualvm 这么强大。我发现某些字符串的计数值很高 - 您的代码是否排除垃圾(未引用的字符串)?
    • 它使用“heap.objects”来查找堆上的所有 java.lang.String 对象。没有过滤来排除未引用的字符串。但是根据堆转储的生成方式,JVM 之前可能已经执行了一次完整的 GC,在这种情况下,任何未引用的字符串都应该已经被删除并且不包含在堆转储中。
    • @JohanKaving 我也试过你的 OQL,但当我过滤掉java.lang.ref.Finalizer 所指对象。我不知道如何在这里实现地图功能。正如你所说,输出包含一些重复的字符串“null”这很奇怪,因为查询应该只打印一次而且它不是这里的地图。
    【解决方案2】:

    我会改用Eclipse Memory Analyzer

    【讨论】:

    • 我真的很喜欢你的建议,因为它很好地解决了问题。然而,我希望你会明白,赏金是给 Johan Kaving 写的 oql。我认为在某些情况下,了解 oql 可能很有用。但是谢谢!
    • 使用打开查询浏览器 -> Java 基础 -> 按值分组。对象选择java.lang.String,字段选择value
    【解决方案3】:

    遗憾的是,OQL 中没有“分组依据”的等价物。我假设您说的是 jhat 和 VisualVM 中使用的 OQL。

    不过,还有另一种选择。如果您使用纯 JavaScript 语法而不是“从 y 中选择 x”语法,那么您就可以使用 JavaScript 的全部功能。

    即便如此,获取您正在寻找的信息的替代方法并不简单。例如,这是一个 OQL“查询”,它将执行与您的查询相同的任务:

    var set={};
    sum(map(heap.objects("java.lang.String"),function(heapString){
      if(set[heapString.toString()]){
        return 0;
      }
      else{
        set[heapString.toString()]=true;
        return 1;
      }
    }));
    

    在这个例子中,一个普通的 JavaScript 对象模仿了一个集合(没有重复的集合)。当 map 函数遍历每个字符串时,该集合用于确定该字符串是否已被看到。重复项不计入总数(返回 0),但新字符串计入总数(返回 1)。

    【讨论】:

    • 嗨,彼得,感谢您的查询,它使我进入了方向,但我还没有到达那里:) 通过这个查询,我看到了重复字符串的总数。我想看到的是字符串和重复数字:'foo' 10 次,'bar' 100 次等。看到我试图输出集合的内容,但我只得到奇怪的 jscript 异常..你知道如何实现我想看到的吗?
    【解决方案4】:

    更高效的查询:

    var countByValue = {};
    
    // Scroll the strings
    heap.forEachObject(
      function(strObject) {
        var key = strObject.toString();
        var count = countByValue[key];
        countByValue[key] = count ? count + 1 : 1;
      },
      "java.lang.String",
      false
    );
    
    // Transform the map into array
    var mapEntries = [];
    for (var i = 0, keys = Object.keys(countByValue), total = keys.length; i < total; i++) {
      mapEntries.push({
        count : countByValue[keys[i]],
        string : keys[i]
      });
    }
    
    // Sort the counts
    sort(mapEntries, 'rhs.count - lhs.count');
    

    【讨论】:

      【解决方案5】:

      在为其他参考做类似问题时发布我的解决方案和经验。

      var counts = {};
      var alreadyReturned = {};
      top(
      filter(
          sort(
              map(heap.objects("java.lang.ref.Finalizer"),
                  function (fobject) {
                      var className = classof(fobject.referent)
                      if (!counts[className]) {
                          counts[className] = 1;
                      } else {
                          counts[className] = counts[className] + 1;
                      }
                      return {string: className, count: counts[className]};
                  }),
              'rhs.count-lhs.count'),
          function (countObject) {
              if (!alreadyReturned[countObject.string]) {
                  alreadyReturned[countObject.string] = true;
                  return true;
              } else {
                  return false;
              }
          }),
          "rhs.count > lhs.count", 10);
      

      前面的代码会输出java.lang.ref.Finalizer使用的前10个类。
      提示:
      1. 使用函数XXX的排序功能在我的Mac OS上不工作。
      2. classof 函数可以返回所指对象的类。 (我尝试使用 fobject.referent.toString() -> 这返回了很多 org.netbeans.lib.profiler.heap.InstanceDump。这也浪费了我很多时间。

      【讨论】:

        【解决方案6】:

        方法一

        您可以选择所有字符串,然后使用终端聚合它们。

        1. 在可视化 vm 配置文件中增加 oql 限制
        2. 重启视觉虚拟机
        3. oql 获取所有字符串
        4. 复制粘贴到vim中
        5. 用 vim 宏清理数据,这样就有了
        6. sort | uniq -c 获取计数。

        方法二

        1. 使用工具转储您感兴趣的类的所有字段对象(https://github.com/josephmate/DumpHprofFields 可以做到)
        2. 使用 bash 选择您感兴趣的字符串
        3. 使用 bash 聚合

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多