【问题标题】:Weird garbage production when instantiating Objects in Java在 Java 中实例化对象时产生奇怪的垃圾
【发布时间】:2012-11-21 20:55:10
【问题描述】:

我正在分析java.lang.String 的垃圾行为,看起来每次你在任何类中第一次实例化一个字符串时它总是会产生垃圾。有人知道为什么吗?

public abstract class AbstractTest {

    protected static String SERGIO = "sergio";

    private String someValue;

    public void init() {
        this.someValue = new String(SERGIO);
    }
}

public class Test extends AbstractTest {

    private static String JULIA = "julia";

    private Runtime runtime = Runtime.getRuntime();
    private String anotherValue;
    private String yetAnother;

    private void gc() throws InterruptedException {
        System.gc();
        Thread.sleep(100);
    }

    private long usedMemory() {
        return runtime.maxMemory() - runtime.freeMemory();
    }

    public void test() throws Exception {
        gc();
        this.anotherValue = new String(SERGIO); // a bunch of garbage is created!
        long usedMemory = usedMemory();
        gc();
        long usedMemoryAfterGC = usedMemory();
        System.out.println("Collected: " + (usedMemory - usedMemoryAfterGC));
        gc();
        this.yetAnother = new String(JULIA); // no more garbage
        usedMemory = usedMemory();
        gc();
        usedMemoryAfterGC = usedMemory();
        System.out.println("Collected: " + (usedMemory - usedMemoryAfterGC));
    }

    public static void main(String[] args) throws Exception {
        Test t = new Test();
        t.test();
    }

输出:

已收藏:704336
已收集:0

没关系。第一次创建垃圾,随后的实例化不会产生垃圾。

奇怪的是,当您在超类中强制创建字符串时,它仍然会在您第一次在子类中实例化字符串时在子类中创建垃圾:

public void test() throws Exception {
    gc();
    init(); // creates a String in the superclass
    gc();
    this.yetAnother = new String(JULIA);
    long usedMemory = usedMemory();
    gc();
    long usedMemoryAfterGC = usedMemory();
    System.out.println("Collected: " + (usedMemory - usedMemoryAfterGC));
}

输出:

已收集:348648

知道为什么吗?

(顺便说一下,我在 MAC 和 JDK 1.6.0_37 上运行它)

EDIT1:我稍微修改了代码以明确字符串内部化不是这里的罪魁祸首,至少看起来不像。

EDIT2: 如果在整个代码中将 String 更改为 Object,则会得到相同的垃圾,所以我猜这与 Java 中通过 new 进行对象分配的方式有关.第一次在类中分配对象时,您会得到垃圾。第二次你没有。奇怪的是每个班级

EDIT3:我写了一个blog article,我在其中讨论了如何强制 GC 分析您的应用程序以创建垃圾,就像我在上面的代码中所做的那样。

【问题讨论】:

  • 可能值得注意的是调用 System.gc();不保证垃圾收集器会立即运行。它有点像“嘿,如果你有机会,如果你能跑就好了”
  • 不明白你所说的“垃圾”是什么意思——你期待什么?
  • garbage = 最终将由 gc 收集的堆中已取消引用的对象。如果你注意到了,我不是产生垃圾的人,而是 JVM 在幕后做的。 JDK 类经常这样做,但这次看起来有点奇怪。
  • @user489041 他在 System.gc() 之后正在睡觉,所以 GC 很有可能会运行。看起来是因为正在收集东西......难道 freeMemory() 只是以某种方式损坏了吗?
  • @TomaszNurkiewicz:这会导致它被汇集。所以它对性能很重要。但我不认为这是这个问题的问题

标签: java garbage-collection real-time low-latency


【解决方案1】:

类中的文字字符串将在第一次引用时被“实习”(如果不是之前的话)。 Interning 一般会涉及丢弃 String 的原始版本并使用 interned 版本,在此过程中可能会创建和丢弃更多的对象。

(当然,如果没有特殊的内部挂钩,确实没有可靠的方法来检测任何单个操作中产生了多少垃圾,因此您的测量结果充其量是可疑的。)

【讨论】:

  • 我认为这不是问题所在。 “实习”意味着添加到internal pool。我在实例化之前添加了String s1 = "sergio"; String s2 = "julia";,它对输出没有影响。我用 new 创建的字符串我不是故意内化的,因为我想在堆中创建一个新对象。
  • @Cratylus -- 肯定有实习生。根据 JVM 规范,类中的每个静态定义(文字)字符串都必须在使用之前进行实习。并且,对于从父类“继承”的static final 字符串,该值在子类中被视为文字——编译后不引用父类中的值。如果您对子类执行javap,您将在文字池中看到这些字符串。
  • @HotLicks 正如您在我的代码中看到的那样,我自己并没有在我正在测量的行中进行任何内化。但是我不知道 new String(...) 是否在内部进行任何内部化。对于内部化的字符串,创建的垃圾似乎过多。你是不是也说字符串池不是全局的?内部化的字符串文字与它所在的类有什么关系?类 A 上的字符串文字“foo”与类 B 上的字符串文字“foo”相同,来自同一个字符串池。没有?
  • 不是new String 这样做的,它是对诸如“julia”之类的字符串文字的引用。第一次引用这样的文字时,它必须被实习。 (在某种程度上,实习是从每个类中的单个字符串文字创建一个“全局”字符串常量池的过程。这就是为什么文字“foo”在您引用它的任何地方都将具有相同的地址。)跨度>
  • 好吧,正如您在我的(更新的)代码中看到的那样,我正在测量的行中没有任何内容被内化。因此它看起来不像内部化或字符串池与问题有任何关系。 :(
【解决方案2】:

在阅读彼得的回答here 之后,很明显 TLAB 是罪魁祸首。如果您使用选项-XX:-UseTLAB 禁用TLAB,问题就会消失。根据我从here 了解到的情况,看起来对于 TLAB,一个线程最初分配了一大块内存以避免以后出现竞争条件。我能够通过使用-XX:TLABSize=64m 为它设置更大的大小来证明 TLAB 是罪魁祸首,并看到这个数量被分配了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-24
    • 1970-01-01
    • 1970-01-01
    • 2014-07-12
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多