Java 8 Update 20 的新特性

字符串在任何应用中都占用了大量的内存。尤其数包含独立UTF-16字符的char[]数组对JVM内存的消耗贡献最多——因为每个字符占用2位。

内存的30%被字符串消耗其实是很常见的,不仅是因为字符串是与我们互动的最好的格式,而且是由于流行的HTTP API使用了大量的字符串。使用Java 8 Update 20,我们现在可以接触到一个新特性,叫做字符串去重,该特性需要G1垃圾回收器,该垃圾回收器默认是被关闭的。

字符串去重利用了字符串内部实际是char数组,并且是final的特性,所以JVM可以任意的操纵他们。

对于字符串去重,开发者考虑了大量的策略,但最终的实现采用了下面的方式:

无论何时垃圾回收器访问了String对象,它会对char数组进行一个标记。它获取char数组的hash value并把它和一个对数组的弱引用存在一起。只要垃圾回收器发现另一个字符串,而这个字符串和char数组具有相同的hash code,那么就会对两者进行一个字符一个字符的比对。

如果他们恰好匹配,那么一个字符串就会被修改,指向第二个字符串的char数组。第一个char数组就不再被引用,也就可以被回收了。

这整个过程当然带来了一些开销,但是被很紧实的上限控制了。例如,如果一个字符未发现有重复,那么一段时间之内,它会不再被检查。

那么该特性实际上是怎么工作的呢?首先,你需要刚刚发布的Java 8 Update 20,然后按照这个配置: -Xmx256m -XX:+UseG1GC 去运行下列的代码:

public class LotsOfStrings {
 
  private static final LinkedList<String> LOTS_OF_STRINGS = new LinkedList<>();
 
  public static void main(String[] args) throws Exception {
    int iteration = 0;
    while (true) {
      for (int i = 0; i < 100; i++) {
        for (int j = 0; j < 1000; j++) {
          LOTS_OF_STRINGS.add(new String("String " + j));
        }
      }
      iteration++;
      System.out.println("Survived Iteration: " + iteration);
      Thread.sleep(100);
    }
  }
}

这段代码会执行30个迭代之后报OutOfMemoryError。

现在,开启字符串去重,使用如下配置去跑上述代码:

-Xmx256m -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics

此时它已经可以运行更长的时间,而且在50个迭代之后才终止。

JVM现在同样打印出了它做了什么,让我们一起看一下:

[GC concurrent-string-deduplication, 4658.2K->0.0B(4658.2K), avg 99.6%, 0.0165023 secs]
  [Last Exec: 0.0165023 secs, Idle: 0.0953764 secs, Blocked: 0/0.0000000 secs]
      [Inspected:          119538]
        [Skipped:              0(  0.0%)]
        [Hashed:          119538(100.0%)]
        [Known:                0(  0.0%)]
        [New:            119538(100.0%)  4658.2K]
      [Deduplicated:      119538(100.0%)  4658.2K(100.0%)]
        [Young:              372(  0.3%)    14.5K(  0.3%)]
        [Old:            119166( 99.7%)  4643.8K( 99.7%)]
  [Total Exec: 4/0.0802259 secs, Idle: 4/0.6491928 secs, Blocked: 0/0.0000000 secs]
      [Inspected:          557503]
        [Skipped:              0(  0.0%)]
        [Hashed:          556191( 99.8%)]
        [Known:              903(  0.2%)]
        [New:            556600( 99.8%)    21.2M]
      [Deduplicated:      554727( 99.7%)    21.1M( 99.6%)]
        [Young:            1101(  0.2%)    43.0K(  0.2%)]
        [Old:            553626( 99.8%)    21.1M( 99.8%)]
  [Table]
      [Memory Usage: 81.1K]
      [Size: 2048, Min: 1024, Max: 16777216]
      [Entries: 2776, Load: 135.5%, Cached: 0, Added: 2776, Removed: 0]
      [Resize Count: 1, Shrink Threshold: 1365(66.7%), Grow Threshold: 4096(200.0%)]
      [Rehash Count: 0, Rehash Threshold: 120, Hash Seed: 0x0]
      [Age Threshold: 3]
  [Queue]
      [Dropped: 0]

为了方便,我们不需要自己去计算所有数据的加和,使用方便的总计就可以了。

上面的代码段规定执行了字符串去重,花了16ms的时间,查看了约 120 k 字符串。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/b26ca448ee12113ed3468bdd0aaeb9fa.html