StringBuilder 释放内存的最快方法

7
什么是释放 StringBuilder 内存的最快方法?
 StringBUilder sb = new StringBuilder();
 sb.apppend(maximum value possible);

以下是我尝试释放内存并使对象尽快成为垃圾回收的代码片段。
选项1:
sb = null;

据我了解,一旦使用StringBuilder对象,其中的所有数据都将被删除,并且对象将成为垃圾回收的对象。但是,StringBuilder占用的文本内存将被释放。它内部的字符串值是否也会从堆中删除或存储在字符串池中?
 sb.setLength(0);

这将重置字符串构建器的长度,但不会被垃圾回收。
选项3:
 sb.delete(0,sb.length());

这将通过删除其中的所有数据来重置字符串构建器,但它不会被垃圾回收。

选项4:

 sb.delete(0,sb.length());
 sb = null;

这将重置字符串生成器并使其符合垃圾回收的条件!

你无法保证内存会在任何特定时间释放,甚至可能根本不会释放。这完全取决于垃圾收集器,并且取决于特定的JVM实现和配置。 - Klitos Kyriacou
1
如果释放内存是您的首要关注点,您可以编写本地代码,将其与JNI链接并自行管理内存,但这样做会削弱使用Java的许多目的。就像GhostCat所问的那样,为什么? - nandsito
正则表达式字符串池:StringBuilder的内容永远不会进入其中。 绝对不会。 只有实际的String才会进入其中,而且只有在运行时通过调用.intern()将它们明确放置在那里,或者编译器在编译时将它们放置在那里。 - Kevin Anderson
5个回答

6
如果您真的希望尽可能快地释放内存,那么您需要查看GC选项而不是代码。
针对此目的的GC选项的详细分析超出了此处的范围 - 但具体来说,新的Shenandoah GC可以被调整为比G1或并发标记/清除更积极地释放内存,因此这可能是您想要使用的。 您可以使用-XX:+UseShenandoahGC指定此选项。(它是OpenJDK特定的实验性功能,但如果您想要的行为是这样的,那么这是您应该选择的选项。)
对于快速释放时间,您需要为ShenandoahUncommitDelayShenandoahGuaranteedGCInterval使用较小的值。 在这里使用较小的值(大体上)将更频繁且更积极地运行GC,因此使用更多的CPU周期 - 但它将产生您想要的效果,即与以前的GC相比,内存将被释放得非常快。
如果您只想确保内存被释放,以便很快成为垃圾回收的候选对象,则只需确保最早的机会将对该StringBuilder的所有引用设置为null。 即使在这种情况下,我也鼓励您进行分析和检查是否需要明确执行此操作 - 在许多情况下,JVM现在足够聪明,即使它们在技术上仍然在作用域内(只要它能看到它们在该作用域的其余部分中不再被引用)。

1
@GhostCat 是的,OpenJDK。我应该在答案中注明它不是标准的(而且是实验性的)。 - Michael Berry
我们开始吧...;-) - GhostCat
@MichaelBerry 很棒的回答!只是请注意,并非所有的jdk版本都有Shenandoah(甚至不能启用它们),而且 ShenandoahUncommitDelay 仅释放在此时间段内为空的区域。 - Eugene

4
简单:使用Go。
sb = null;

只需关注上面的赋值,并忘记其他事情。

为什么?一旦GC可以确定对象适合垃圾回收,它也知道从该对象引用的所有东西都是可用的。而且考虑到你无法控制GC何时清理对象并释放内存,上述赋值是非常好的。

因为它清楚地传达了您的意图,然后帮助GC执行其工作。实际上,写得很好的代码可能甚至不需要那个语句。

现在,也许这还不够好。但是,这将涉及到一个非常明确定义和狭窄的真正性能问题。在这种情况下,您不能依靠道听途说,您要开始测量。

换句话说:如果您的设置中没有真正的表现出来需要采取行动的性能问题,那么您就使用简单、清晰的代码,避免各种“源代码级”优化。但是,如果您有一个真正的性能问题,那么您就相应地处理它:通过识别根本原因,并可能通过测量不同选项在实践中的效果来处理它。

任何其他东西都会让人想起过早的优化!


3
确保 StringBuilder 符合垃圾收集条件的唯一安全方法与所有对象相同:删除对它的所有引用。这意味着显式将其设置为 null 或让其超出范围。
除此之外,您无法控制内存何时释放。你能做的最多就是请求垃圾回收,这很少是一个好主意(更不用说你只是请求而不是命令JVM进行垃圾回收)。
您提出的其他选择不能保证释放所有内存,除非您深入了解了代码并确信已完全刷新了对象的状态。这就是为什么通常最好只将整个对象引用设置为null并重新开始,以确保一切都“干净”。

3

只需让StringBuilder对象超出范围(在右括号处)并且不要用不必要的语句来使垃圾收集器更快地工作。当StringBuilder对象超出范围时,它将变得可供垃圾回收。保持源代码整洁。


没错!我也想这样,但是前辈们很难理解 :) - Ahmad Qureshi
1
听起来你的前辈们来自一种具有自己范式的不同编程语言。我会温和地向他们解释,这真的不是Java的工作方式。 - BeUndead

0
一个可能的解决方案包括以下3个步骤:
  1. 调用 sb.setLength(0)。这将设置字符串构建器的内部长度为0,并且是第二步所需。
  2. 调用 sb.trimToSize()。这将替换value数组为新的修剪后的数组,在我们的情况下是大小为0char[]
  3. sb = null 将删除对StringBuilder的引用。

如果字符串构建器仅由您使用,并且未传递给其他地方,则应使以前的value数组和字符串构建器可供垃圾回收。

尽管具体实现取决于JVM,因此“最快的方式”实际上相当随机。


2
你也可以调用 System.gc(); 来指示 JVM 优先进行垃圾回收... 但这并不保证有效... 而且实际上也不应该必要。请不要为了你的合作者们的共同理智而这样做。 - BeUndead
1
垃圾回收后,他们应该释放它。我相信早期这样做的唯一方法是使用sun.misc.Unsafe - freeMemory方法。然而,这真的不是一个好主意...请考虑为什么您认为需要快速释放内存。 - BeUndead
1
@Lino "调用sb.setLength(0)。这将使用\0(“null”-char)填充字符串构建器内的值数组" -- 仅当您使用大于当前长度的参数调用setLength时才会发生这种情况。SetLength(0)与OP想要的内容无关。 - Klitos Kyriacou
1
@Lino 你的编辑并没有反映出我的意思。即使你当前的字符串有20个字符,当你调用 setLength(0) 时,它除了将count设置为0之外,仍然不会做任何事情。将未使用的字符设置为 '\0' 没有任何意义;这只会浪费CPU时间。只有当你 扩展 字符串以使其比当前更大时,它才会将字符设置为0。 - Klitos Kyriacou
1
@KlitosKyriacou 你是对的,我已经纠正了我的回答,set长度只是用于第二步,因为trimToSize()会用空数组替换原始数组。 - Lino
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接