标签:
原文链接: java-performance 翻译: ImportNew.com - 夏千林
译文链接: http://www.importnew.com/7656.html
ChangeLog:
共享一个基础char[]
原先的String类中有4个非静态变量:
正如你所看到的,绝大多数String对象都会是offset=0并且count=value.length。除非通过调用String.substring方法创建的String对象,或是间接调用Pattern.split这类API创建对象。
String.substring创建的String对象将和原String对象共享同一个内部变量char[] value,这样设计的好处是:
然而,这样的设计有可能会导致内存泄露:如果你从一个长度很长的String对象中提取出一个很短的子串,当这个String对象不再需要时(该对象静候GC回收),你的子串中还保持着这个String对象中存储着完整字符串的char[] value数组的引用。这种情况的解决方法是通过构造函数new String(String)创建一个新的子串对象,进而解除短子串与长母串之间的依赖关系。
自Java 1.7.0_06版本起(包括Java 8的最新版本),String类中不再有offset和count变量。这意味着成员变量char[] value将不会被共享。你可以忘记上述有关内存泄露的描述以及如何使用new String(String)方法来避免内存泄露的发生。但需要记住的是,String.substring现在是线性级的时间复杂度,不再是常数级的时间复杂度。
哈希算法的改变
下面的部分仅适用于Java 7u6以上的Java 7版本,这些代码在Java 8中已被删除。
String类在本次更新中的另一个变化是一个新的哈希算法。Oracle表示新的算法能生成出更好的哈希分布,并且能够提高基于哈希算法的容器的性能,如HashMap、Hashtable、HashSet、 LinkedHashMap、LinkedHashSet、WeakHashMap和ConcurrentHashMap 。与本文第一部分介绍的变化不同,这部分变化是试验性质的,默认是关闭的。
你可能已经猜到了,这部分变化只适用于String类型的Key。如果想要启用它们,需要将系统变量jdk.map.althashing.threshold设置为一个非负的整数值(默认为-1)。当使用新的哈希方法时,这个值将是容器大小的阈值。这里需要注意的是:哈希方法只有在进行重哈希(rehash)的时候才会被更新。因此,如果容器上次执行重哈希是在size=160的时候,而jdk.map.althashing.threshold = 200,这样只有在容器的size增长到大约320的时候,哈希方法才会被更新。
String类现在有一个hash32()方法,它的结果被缓存在成员变量int hash32 中。这个方法最大的变化是,同一个字符串在不同的JVM上执行hash32()的结果可能不同(确切的说,多数情况下都会不同,因为其内部分别调用了一次System.currentTimeMillis()和两次System.nanoTime()用于初始化seed)。因此,某些容器在每次运行程序时迭代顺序都不同。
事实上,我对这个方法的改变有一点意外。如果原先的hashCode方法运行的很好,为什么我们需要一个新的哈希方法呢?我决定使用文章hashcode方法性能调优中的测试程序,测试一下使用hash32方法会产生多少个重复的哈希值。
String.hash32()方法不是公有的,因此我只能通过查看HashMap的源码来找到调用String.hash32()的方法。答案是issun.misc.Hashing.stringHash32(String)。
使用同一数据集(由1百万个各不相同的Key组成)进行测试,String.hash32生成了304个重复的哈希值,而相比之下String.hashCode并没有生成重复的哈希值。我想我们需要静候Oracle进一步的完善或者更多的使用场景说明。
新的哈希算法可能会严重影响高并发、多线程代码
本章节适用于Java 7版本的build 6(包含build 6)至build40(不包含build40)。这部分代码在Java 8中已被删除。有关Java 7u40以上版本的相关介绍请参见下一个章节。
Oracle在后面这些类的哈希实现中遗留了一个bug: HashMap、Hashtable、HashSet、LinkedHashMap、LinkedHashSet和WeakHashMap,只有ConcurrentHashMap 不受影响。这是因为所有的非concurrent类中现在都引入了下面的成员变量:
1
2
3
4
5
|
/** * A randomizing value associated with this instance that is applied to * hash code of keys to make hash collisions harder to find. */ transient final int hashSeed = sun.misc.Hashing.randomHashSeed( this ); |
这意味着每一个map或set实例创建的过程中都会调用sun.misc.Hashing.randomHashSeed方法。randomHashSeed后续会调用java.util.Random.nextInt方法。Random类以其多线程环境下不友好而闻名:它有一个Atomic类型的成员变量private final AtomicLong seedfield。Atomic类型在多线程竞争程度较低或者适中的场景下性能表现较好,但在竞争激烈的场景下性能很差。
因此,很多处理HTTP/JSON/XML请求的高负载Web应用可能会被这个bug所影响,因为现有的解析器在表示名值对(name-value)时几乎都使用了上述的存在bug的容器。这些解析器还很可能使用了嵌套的map,这会进一步增加每秒中创建map实例的数量。
如何解决这一问题呢?
1. 使用ConcurrentHashMap :只有在设置系统变量jdk.map.althashing.threshold时才会调用randomHashSeed 方法。但很可惜的是,这种方式仅适用于JDK的核心开发者。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** * A randomizing value associated with this instance that is applied to * hash code of keys to make hash collisions harder to find. */ private transient final int hashSeed = randomHashSeed( this ); private static int randomHashSeed(ConcurrentHashMap instance) { if (sun.misc.VM.isBooted() && Holder.ALTERNATIVE_HASHING) { return sun.misc.Hashing.randomHashSeed(instance); } return 0 ; } |
2. Hacker的方式:修改sun.misc.Hashing类,这种方式极度不推荐。但如果你依然想解决这个bug,解决的思路是:java.util.Random类并不是final的。你可以在Java 7中加入Random 类的一个Thread Local的子类:java.util.concurrent.ThreadLocalRandom,它内部使用了ThreadLocal<ThreadLocalRandom>(感谢Benjamin Possolo指出我在之前的文章中遗漏了这个类的介绍)。除此之外,ThreadLocalRandom属于CPU cache感知型:每个ThreadLocalRandom实例的seed后面增加了64字节的填充(cache行的大小),进而降低2个不同的seed在同一个cache行中碰撞的可能性。
然后你可以修改成员变量sun.misc.Hashing.Holder.SEED_MAKER,将它初始化为Random子类的实例(ThreadLocalRandom)。不用担心这个变量是私有的、静态的而且是final的,反射机制可以帮你。
1
2
3
|
public class Hashing { private static class Holder { static final java.util.Random SEED_MAKER; |
Java 7u40以上的版本中新的哈希算法不再影响高并发、多线程代码
Oracle在Java 7u40版本中修正了上述的bug。
他们使用了上一章节中提到的方法一,仅在重哈希阀值被启用时(通过设置系统变量jdk.map.althashing.threshold启用)才调用sun.misc.Hashing.randomHashSeed方法。Oracle只修改了两个类:HashMap和Hashtable,进而间接修改了 HashSet、LinkedHashMap和LinkedHashSet,因为这三个类是基于HashMap实现的。唯一没有被修改的类是WeakHashMap,但我实在想不出这个类会被大量实例化的应用场景。
相关文章
最近,本文在Reddit上引起了激烈的讨论。我推荐读者们去看一看:
总结
Java 1.7.0_06中String类内部实现的一些变化【转】
标签:
原文地址:http://www.cnblogs.com/ikuman/p/4480999.html