什么是重复的字符串?25%的Java应用程序内存中填充了字符串,其中13.5%是重复的字符串。在本文中,Ram Lakshmanan讨论了为什么有这么多重复的字符串,常见的模式是什么,以及如何处理它。
让我从一篇有趣的统计数据开始本文(基于JDK开发团队的研究):
+ 25%的Java应用程序内存中充满了字符串。 + 13.5%是Java应用程序中的重复字符串。 + 平均字符串长度为45个字符。
您可以使用 HeapHero之类的 工具,它可以报告由于重复字符串和其他低效编程实践而浪费了多少内存。
什么是重复的字符串?
首先,让我们了解重复字符串的含义。请看下面的代码片段:
String string1 = <b>new</b> String(<font>"Hello World"</font><font>); String string2 = <b>new</b> String(</font><font>"Hello World"</font><font>); </font>
在上面的代码中有两个字符串对象string1和string2它们具有相同的内容,即“Hello World”,但它们存储在两个不同的对象中。当你执行string1.equals(string2)时,它将返回'true',但'string1 == string2'将返回'false'。这就是我们所说的重复字符串。
为什么有这么多重复的字符串?
应用程序最终会出现大量重复字符串的原因有多种。在本节中,我们将回顾两种最常见的模式:
1. 开发人员为每个请求创建新的字符串对象,而不是引用/重用“public static final string literal”。上面的示例可以使用字符串文字模式进行最佳编写:
<b>public</b> <b>static</b> <b>final</b> String HELLO_WORLD = <font>"Hello World"</font><font>; String string1 = HELLO_WORLD; String string2 = HELLO_WORLD; </font>
2.假设您正在构建银行/电子商务应用程序。您正在为数据库中的每个交易记录存储货币(即“USD”,“EUR”,“INR”,......)。现在说客户登录您的应用程序,他正在查看他的交易历史页面。现在,您的应用程序将最终从数据库中读取与此客户相关的所有事务。假设这个客户住在美国(那么大部分(如果不是全部)他的交易将以美元为单位)。由于每个交易记录都有货币,因此您的应用程序最终会为从数据库读取的每个交易记录创建一个“USD”字符串对象。如果这个客户有成千上万的交易,你最终会在内存中创建数千个重复的'USD'字符串对象,这对于这一个客户来说也是如此。
同样,您的应用程序可能会多次从数据库中读取多个列(客户名称,地址,州,国家/地区,帐号,ID,......)。它们之间可能存在重复。您的应用程序使用外部应用程序读取和写入XML / JSON,它操纵了很多字符串。所有这些操作都可以/将创建重复的字符串。
自从JDK团队组建开始(20世纪90年代中期)以来,这个问题一直被人们认可,因此到目前为止已经提出了多种解决方案。此解决方案列表的最新成员是'-XX:+ UseStringDeduplication'
-XX:+UseStringDeduplication
尝试消除重复字符串的最小努力是传递'-XX:+ UseStringDeduplication'JVM参数。在应用程序启动期间传递此JVM参数时,JVM将尝试在垃圾收集过程中消除重复的字符串。在垃圾收集过程中,JVM会检查内存中的所有对象,因此作为该过程的一部分,它会尝试识别它们中的重复字符串并尝试消除它。
是否意味着如果你只是传递'-XX:+ UseStringDeduplication'JVM参数你能立即节省13.5%的内存吗?:-)听起来很容易,对吧?我们希望这很容易。但是这个'-XX:+ UseStringDeduplication'解决方案有一些问题。我们来讨论一下:
1.仅适用于G1 GC算法
有几种垃圾收集算法(串行,并行,CMS,G1,......)。'-XX:+ UseStringDeduplication'仅在您使用G1 GC算法时有效。因此,如果您使用其他GC算法,则需要切换到G1 GC算法以使用'-XX:+ UseStringDeduplication'。
2.仅适用于长寿命对象
'-XX:+ UseStringDeduplication'消除了较长时间内存在的重复字符串。它们不会消除短期字符串对象中的重复字符串。如果对象是短暂的,它们将很快消失,那么花费资源消除它们中的重复字符串的重点是什么。以下是对主要Java Web应用程序进行的真实案例研究,当使用'-XX:+ UseStringDeduplication'时,该应用程序没有显示任何内存缓解。但是,如果您的应用程序有很多缓存,那么'-XX:+ UseStringDeduplication'可能是有价值的(因为缓存对象通常往往是长期存在的对象)。
3. -XX:StringDeduplicationAgeThreshold
默认情况下,如果字符串在3次GC运行中幸存,则符合重复数据删除的条件。可以通过传递'-XX:StringDeduplicationAgeThreshold'来更改3次这个参数。例:
-XX:StringDeduplicationAgeThreshold=6
4.对GC暂停时间的影响
由于在重复垃圾收集期间执行字符串重复数据删除,因此可能会影响GC暂停时间。但是,假设足够高的重复数据删除成功率将平衡大部分或全部这种影响,因为重复数据删除可以减少GC暂停的其他阶段所需的工作量(例如减少撤离的对象数量)以及减少GC频率(由于堆上的压力减小)。
要分析GC暂停时间影响,您可以考虑使用 GCeasy等 工具。
5.只替换底层char []
java.lang.String类有两个字段:
<b>private</b> <b>final</b> <b>char</b>[] value <b>private</b> <b>int</b> hash
'-XX:+ UseStringDeduplication'不会消除重复的字符串对象本身。它只替换了底层的char []。对String对象进行重复数据删除在概念上只是对value字段的重新赋值,即aString.value = anotherString.value。
每个字符串对象至少需要24个字节(字符串对象的确切大小取决于JVM配置,但最少24个字节)。因此,如果存在大量短重复字符串,则此功能可以节省更少的内存。
6. Java 8 update 20
'-XX:+ UseStringDeduplication'功能仅受Java 8 update 20中支持。因此,如果您在任何旧版本的Java上运行,您将无法使用此功能。
7. -XX:+ PrintStringDeduplicationStatistics
如果您希望查看字符串重复数据删除统计信息,例如运行所花费的时间,撤出的重复字符串数量,您获得了多少节省,您可以传递'-XX:+ PrintStringDeduplicationStatistics'JVM参数。在错误控制台中将打印统计信息。
结论
如果您的应用程序使用G1 GC并运行在Java 8更新20之上的版本,您可以考虑启用'-XX:+ UseStringDeduplication'。您可能会获得富有成效的结果,尤其是在长寿命对象中存在大量重复字符串的情况下。但是,在生产环境中启用此参数之前,请进行全面测试。