在Java中实例化对象时产生奇怪的垃圾

在Java中实例化对象时产生奇怪的垃圾,java,garbage-collection,real-time,low-latency,Java,Garbage Collection,Real Time,Low Latency,我正在分析java.lang.String的垃圾行为,每次您在任何类中第一次实例化一个字符串时,它都会生成垃圾。有人知道为什么吗 public abstract class AbstractTest { protected static String SERGIO = "sergio"; private String someValue; public void init() { this.someValue = new String(SERGIO)

我正在分析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和JDK1.6.037上运行这个

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

EDIT2:如果在整个代码中将字符串改为Object,则会得到相同的垃圾,因此我猜这与Java中通过new进行对象分配的方式有关。第一次在类中分配对象时,会得到垃圾。第二次你没有。奇怪的是每节课都有


EDIT3:我写了一篇文章,其中谈到了如何强制GC分析您的应用程序以创建垃圾,就像我在上面的代码中所做的那样。

类中的文本字符串将在第一次引用时插入,如果不是之前的话。插入通常包括丢弃字符串的原始版本并使用插入的版本,在此过程中可能会创建和丢弃更多的对象


当然,如果没有特殊的内部钩子,实际上没有可靠的方法来检测在任何单个操作中生成了多少垃圾,因此您的度量充其量是可疑的。

类中的文本字符串将在第一次引用时被插入,如果不是在第一次引用之前。插入通常包括丢弃字符串的原始版本并使用插入的版本,在此过程中可能会创建和丢弃更多的对象


当然,如果没有特殊的内部钩子,没有可靠的方法来检测在任何一次操作中产生了多少垃圾,因此您的测量结果充其量也是可疑的。

阅读Peter的答案后,很明显TLAB是罪魁祸首。如果使用选项-XX:-UseTLAB禁用TLAB,问题就会消失。从我的理解来看,对于TLAB,线程一开始会分配一大块内存,以避免以后出现争用情况。我能够证明TLAB是罪魁祸首,方法是将其设置为-XX:TLABSize=64m的更大尺寸,并查看分配的金额。

阅读Peter的答案后,很明显TLAB是罪魁祸首。如果使用选项-XX:-UseTLAB禁用TLAB,问题就会消失。从我的理解来看,对于TLAB,线程一开始会分配一大块内存,以避免以后出现争用情况。我通过将TLAB的大小设置为-XX:TLABSize=64m来证明TLAB是罪魁祸首,并查看分配的金额。

可能值得注意的是,调用System.gc;不保证垃圾收集器将立即运行。这有点像一个嘿,当你有机会的时候,如果你不明白垃圾是什么意思,那就太好了-你期待什么?垃圾=堆中最终将由gc收集的取消引用的对象。如果您注意到了,我不是产生垃圾的那个人,但是JVM正在幕后进行这项工作。JDK类经常这样做,但这次看起来有点奇怪。@user489041他在System.gc之后睡觉,所以gc很有可能会运行。看起来是因为东西被收集起来了。。。可能是因为空闲内存被破坏了吗?@TomaszNurkiewicz:这会导致它被合并。所以这关系到性能。但我不认为这是这个问题中的问题。可能值得注意的是调用System.gc;不保证垃圾收集器将立即运行。这有点像一个嘿,当你有机会的时候,如果你不明白垃圾是什么意思,那就太好了-你期待什么?垃圾=堆中最终将由gc收集的取消引用的对象。如果您注意到了,我不是产生垃圾的那个人,但是JVM正在幕后进行这项工作。JDK类经常这样做,但这次看起来有点奇怪。@user489041他在System.gc之后睡觉,所以gc很有可能会运行。看起来是因为东西被收集起来了。。。可能是因为freeMemory被破坏了吗?@TomaszNurkiewicz:这会导致它被合并。所以这关系到性能。但我不认为这是这个问题中的问题,我不认为这是这里的问题。实习意味着增加了实习机会。我添加了字符串s1=sergio;字符串s2=julia;在实例化之前,它对输出没有影响。str
我用new创建的对象不是有意内部化的,因为我希望堆中有一个新对象。@Cratylus-肯定有一个实习生。根据JVM规范,类中每一个静态定义的文本字符串在使用之前都必须先被插入。并且,对于从父类继承的静态最终字符串,该值在子类中被视为文本-编译后不引用父类中的值。如果在子类上执行javap,您将在文本池中看到这些字符串。@HotLicks我自己并没有像您在代码中看到的那样,在我测量的行中进行任何内部化。但是我不知道新字符串是否。。。正在内部进行任何内部化。对于内部化的字符串,创建的垃圾似乎过多。你是说字符串池不是全局的吗?内部化的字符串文字与它所在的类有什么关系?类A上的字符串文字foo与类B上的字符串文字foo是同一个对象,来自同一个字符串池。不是吗?这不是一个新字符串,它是对字符串文本的引用,比如julia。第一次引用这样的文本时,必须将其插入。在某种程度上,Interning是从每个类中的单个字符串文本创建字符串常量的全局池的过程。这就是为什么文字foo在任何引用它的地方都有相同的地址。正如您在我更新的代码中看到的,在我测量的行中没有任何内容被内部化。因此,看起来内部化或字符串池与问题无关我认为这不是问题所在。实习意味着增加了实习机会。我添加了字符串s1=sergio;字符串s2=julia;在实例化之前,它对输出没有影响。我用new创建的字符串不是有意内部化的,因为我希望堆中有一个新对象。@Cratylus-肯定有一个实习生。根据JVM规范,类中每一个静态定义的文本字符串在使用之前都必须先被插入。并且,对于从父类继承的静态最终字符串,该值在子类中被视为文本-编译后不引用父类中的值。如果在子类上执行javap,您将在文本池中看到这些字符串。@HotLicks我自己并没有像您在代码中看到的那样,在我测量的行中进行任何内部化。但是我不知道新字符串是否。。。正在内部进行任何内部化。对于内部化的字符串,创建的垃圾似乎过多。你是说字符串池不是全局的吗?内部化的字符串文字与它所在的类有什么关系?类A上的字符串文字foo与类B上的字符串文字foo是同一个对象,来自同一个字符串池。不是吗?这不是一个新字符串,它是对字符串文本的引用,比如julia。第一次引用这样的文本时,必须将其插入。在某种程度上,Interning是从每个类中的单个字符串文本创建字符串常量的全局池的过程。这就是为什么文字foo在任何引用它的地方都有相同的地址。正如您在我更新的代码中看到的,在我测量的行中没有任何内容被内部化。因此,看起来内部化或字符串池与问题无关