我相信当我在整个应用程序中的各个地方调用new String
时会创建这个垃圾。我怎样才能创造"一个字符串,每次都不创建新对象?
这种垃圾敏感的原因是因为我的应用程序无法创建垃圾,因为我们需要使用默认的Java GC接近实时运行。
// you can see I use the same chars array
public String getB37String() {
long l = getLong();
int i = 0;
while (l != 0L) {
long l1 = l;
l /= 37L;
chars[11 - i++] = validChars[(int) (l1 - l * 37L)];
}
return new String(chars, 12 - i, i);
}
例如,使用下面使用StringBuilder.toString()
的{{1}}。
new String
答案 0 :(得分:6)
首先观察:
这种垃圾敏感的原因是因为我的应用程序无法创建垃圾,因为我们需要使用默认的Java GC接近实时运行。
如果那个(“不能创建垃圾”)实际上是一个真实的声明 1 ,那么你可能已经开始在错误的地方通过选择Java作为您的实现语言。
Java的设计假设垃圾的生成是可以的。这是避免执行显式内存管理的固有复杂性(以及随之而来的错误)的“成本”。这种假设贯穿于语言设计和标准库设计中。
关于Java的另一个不是“对你有利”的事情是它强烈支持良好的面向对象设计原则。特别是,除少数例外情况外,API提供了强大的抽象功能,旨在防止应用程序意外破坏事物的陷阱。
例如,执行此操作时:
char[] c = new char[]{'a', 'b', 'c'};
...
String s = new String(c);
String
构造函数分配一个新的char[]
并复制到c
中的字符。为什么?因为如果没有,你会有一个“漏洞抽象”。有人可以这样做:
char[] c = new char[]{'a', 'b', 'c'};
...
String s = new String(c);
...
c[0] = 'd';
并且漏洞抽象导致了对(假设的)不可变对象的更改。
那么什么是“解决方案”?
您可以使用C或C ++或其他一些编程语言重写您的应用程序,您可以完全控制内存分配。 (当然,这是很多工作......而且可能还有其他原因导致你无法做到这一点。)
您可以重新设计应用程序的相关部分,以便它们不使用String
或StringBuilder
或任何涉及显式或隐式的标准Java类(引擎盖下)堆分配。这不是不可能的,但它需要做很多工作。例如,许多标准和第三方API都希望您将String
个对象作为参数传递。
您可以分析执行字符串操作的代码部分,使其“更智能”,以便减少垃圾分配。
不幸的是,所有这些都可能使你的代码库更大,更难阅读,更难调试,更难维护。
1 - 如果你真正试图解决的问题是GC暂停,那么可能不是真的一种情况。有一些方法可以解决GC暂停问题,但这些问题并没有产生任何垃圾。例如,选择一个低暂停并行GC,并减小年轻代空间的大小,可能会给你一些短暂到不明显的停顿。另一个技巧是当你知道用户不会注意到时,强制GC点数;例如在游戏中加载新关卡时。 sup>
答案 1 :(得分:2)
引用为here。
它们都是一样的,它们和其他任何对象一样,但是:
由于String是任何应用程序中最常用的类型之一,因此Java 设计师更进一步优化了类的使用。那是 为什么他们想出了一个缓存创建的所有String实例的想法 内部双引号,例如
"Java"
。这些双引号字面是 称为String literal和存储这些String的缓存 实例称为字符串池。在高级别,
String
对象都有,但主要区别在于 从new()
运算符始终创建新String开始 宾语。此外,当您使用文字创建字符串时,它们将被实现。String a = "Java"; String b = "Java"; System.out.println(a == b); // True
这里创建了两个不同的对象,它们有所不同 引用:
String c = new String("Java"); String d = new String("Java"); System.out.println(c == d); // False
类似地将String文字与String对象进行比较 使用new()运算符使用==运算符创建,它将返回false, 如下图所示:
String e = "JDK"; String f = new String("JDK"); System.out.println(e == f); // False
引用为here。
实际上String对象与String文字对应 通常不是垃圾收集的候选人。这是因为 在代码中有一个对字符串对象的隐式引用 每个使用文字的方法。这意味着String是 只要方法可以执行就可以到达。
然而,情况并非总是如此。如果文字是在a中定义的 动态加载的类(例如,使用Class.forName(...)), 然后可以安排卸载类。如果说 发生,那么文字的String对象将无法访问, 并且当包含interned String的堆时将被回收 得到GC'ed。
尊敬是here。
java.lang.String.intern()
返回一个实习字符串,即一个 在全局字符串池中有一个条目。如果字符串不是 已经在全局字符串池中,然后它将被添加。
以编程方式,您可以遵循以下方法:
s
和t
,s.intern() == t.intern()
当且仅当s.equals(t)
为真时才为真。 因此,如果您在字符串上使用intern()
:
String.intern()
然后:
答案 2 :(得分:2)
如果您使用的是Java8u20或更新版本,可以尝试使用-XX:+UseG1GC -XX:+UseStringDeduplication
启用string deduplication。
虽然这不会避免产生垃圾,但可能会降低内存压力。
如果您确实要创建String
个实例而不需要char[]
数组的复制费用,则必须访问包私有构造函数java.lang.String.String(char[], boolean)
或私有char[] value
通过反射进行字段,并通过适当的运行时检查/错误报告它是否真的有效。
我不推荐它,但它是一种选择。
另一个选择是停止使用字符串并使用ByteBuffer
。您可以根据需要对它们进行切片,返回视图,返回只读视图,然后回收它们。
如果您使用utf-8数据,它们也会更加紧凑。缺点是您无法使用需要字符串的API。
或者只是在尽可能多的地方处理CharSequence / StringBuilder / Charbuffer对象。
根据用例,您还可以为计算创建字符串缓存。 Map<T, String>
其中T
是计算的输入参数。这样,对于T
的每个可能值,您只需要1个字符串。
return new String(chars, 12 - i, i);
请注意,从Java 8开始,字符串不存储内部偏移量,即String对象不是&#34; view&#34;在一些可能更大的支持字符数组上。
过去曾经有所不同,但由于它是一个实现细节,所以它已经改变了。
可以通过bootstrap类加载器添加自定义String类来撤消该更改,但这样做更容易破坏或导致严重的性能下降。
因为我们需要使用默认的Java GC接近实时运行。
这可能是您的实际问题。
默认配置的任何收集器都不会为您提供甚至接近实时行为的任何内容。与串行或ParallelOld收集器相比,CMS或G1可以提供更低的暂停时间,尤其是在大堆上。