Java字符串字面量可以进行垃圾回收吗?如果可以,如何证明?

3

Java中的字符串字面值,比如"abc",是否可以被垃圾回收?如果可以,我们如何编程地证明它们已经被回收了?


精神分裂症? :) 开玩笑的,好一个 :) - zubergu
@zubergu - 不是真的..只是有一些空闲时间..想要明智地利用它 :) - TheLostMind
1
让我谨慎地查一下... :D - zubergu
1
可能是字符串字面量的垃圾回收的重复问题。 - weston
@weston - 我看到了那个问题。我想自己编写/查看它.. :) - TheLostMind
@weston - 这个问题没有包含代码来证明发生了什么。因此,这不是一个重复的问题。 - TheLostMind
2个回答

6

是的,在Java7之后,如果加载字符串字面量的类加载器被垃圾收集并且没有对该字符串字面量的引用,则可以对其进行垃圾收集。

注意:在Java-8中,您需要调用两次GC以确保ClassLoaders被GC(Metaspace……使用不同的GC也无济于事)。

Case -1 : 
//ClassLoaders don't get GCed.

Code :
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

// main class
 class TestStringLiteralGC {
    public static void main(String[] args) throws Exception {
        Class<?> c1 = new CustomClassLoader().loadClass("Test"); // load class once
        Class<?> c2 = new CustomClassLoader().loadClass("Test");  // load class again
        System.out.println("c1 : " + c1); // c1 : class Test
        System.out.println("c2 : " + c2); // c2 : class Test 
        System.out.println("c1 == c2 :" + (c1 == c2)); //c1 == c2 :false --> So, now we have 2 different class objects for same class.
        Field f1 = c1.getDeclaredField("s"); // getting field s of c1
        f1.setAccessible(true);
        System.out.println("Identity hashCode of c1.s :"+ System.identityHashCode(f1.get(null))); // Identity hashCode of c1.s :1442407170
        Field f2 = c2.getDeclaredField("s");  // getting field s of c2
        f2.setAccessible(true);
        System.out.println("Identity hashCode of c2.s :"+ System.identityHashCode(f2.get(null))); // Identity hashCode of c2.s :1442407170
        System.out.println("c1.s == c2.s : " + (f1.get(null) == f2.get(null))); // c1.s == c2.s : true ==> c1.s is the same "instance" as c2.s
        //Don't make c1 and c2 eligible for GC
        // So, now, there are still references to "abc"
//      f1 = null;
//      c1 = null;
//      f2 = null;
//      c2 = null;
       //call GC explicitly. Yes, twice.
        Thread.sleep(1000);
        System.gc();
        Thread.sleep(1000);
        System.gc();
        Thread.sleep(1000);
        // use the same string literal in main. Just to test that the same literal is being used.
        String s = "abc";
        System.out.println("Identity hashCode of mainMethod's s : " + System.identityHashCode(s)); // Identity hashCode of mainMethod's s : 1442407170 ==> Yes. The IDHashcodes are the same
    }

}
// Our class which will be loaded
class Test {
    static String s = "abc"; // Our little hero!.The string literal. 
}
//Our custom ClassLoader to load the class "Test"
class CustomClassLoader extends ClassLoader {
    // finalize() is to check if Object is unreachable (and ready for GC)
    protected void finalize() throws Throwable {
        System.out.println("CustomClassLoader finalize called.." + this);
    };

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (!name.equals("Test")) {
            return super.loadClass(name);
        }
        try {
            InputStream in = ClassLoader
                    .getSystemResourceAsStream("Test.class");
            byte[] a = new byte[10000];
            int len = in.read(a);
            in.close();
            return defineClass(name, a, 0, len);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        }
    }
}

O/P :
// NO GC of Classloaders :(
c1 : class Test
c2 : class Test
c1 == c2 :false
Identity hashCode of c1.s :1442407170  // Value- 1
Identity hashCode of c2.s :1442407170  // Value -2
c1.s == c2.s : true
Identity hashCode of mainMethod's s : 1442407170 // Value -3

(1,2)和3的IdentityHashCode相同意味着所有三个位置都使用了相同的字符串常量"abc"。
Case : 2 
//Force GC of ClassLoaders and check again.

Code :
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

// main class
 class TestStringLiteralGC {
    public static void main(String[] args) throws Exception {
        Class<?> c1 = new CustomClassLoader().loadClass("Test"); // load class once
        Class<?> c2 = new CustomClassLoader().loadClass("Test");  // load class again
        System.out.println("c1 : " + c1); // c1 : class Test
        System.out.println("c2 : " + c2); // c2 : class Test 
        System.out.println("c1 == c2 :" + (c1 == c2)); //c1 == c2 :false --> So, now we have 2 different class objects for same class.
        Field f1 = c1.getDeclaredField("s"); // getting field s of c1
        f1.setAccessible(true);
        System.out.println("Identity hashCode of c1.s :"+ System.identityHashCode(f1.get(null))); // Identity hashCode of c1.s :1442407170
        Field f2 = c2.getDeclaredField("s");  // getting field s of c2
        f2.setAccessible(true);
        System.out.println("Identity hashCode of c2.s :"+ System.identityHashCode(f2.get(null))); // Identity hashCode of c2.s :1442407170
        System.out.println("c1.s == c2.s : " + (f1.get(null) == f2.get(null))); // c1.s == c2.s : true ==> c1.s is the same "instance" as c2.s
        //Make c1 and c2 eligible for GC
        // So, now, there are no references to "abc"
        f1 = null;
        c1 = null;
        f2 = null;
        c2 = null;
       //call GC explicitly. Yes, twice.
        Thread.sleep(1000);
        System.gc();
        Thread.sleep(1000);
        System.gc();
        Thread.sleep(1000);
        // use the same string literal in main. Just to test that the same literal is being used.
        String s = "abc";
        System.out.println("Identity hashCode of mainMethod's s : " + System.identityHashCode(s)); // Identity hashCode of mainMethod's s : 1118140819 ==> Oh!!. The IDHashcodes are NOT the same
    }

}
// Our class which will be loaded
class Test {
    static String s = "abc"; // Our little hero!.The string literal. 
}
//Our custom ClassLoader to load the class "Test"
class CustomClassLoader extends ClassLoader {
    // finalize() is to check if Object is unreachable (and ready for GC)
    protected void finalize() throws Throwable {
        System.out.println("CustomClassLoader finalize called.." + this);
    };

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (!name.equals("Test")) {
            return super.loadClass(name);
        }
        try {
            InputStream in = ClassLoader
                    .getSystemResourceAsStream("Test.class");
            byte[] a = new byte[10000];
            int len = in.read(a);
            in.close();
            return defineClass(name, a, 0, len);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        }
    }
}

O/P :

c1 : class Test
c2 : class Test
c1 == c2 :false
Identity hashCode of c1.s :1442407170 // Value - 1
Identity hashCode of c2.s :1442407170 // Value - 2
c1.s == c2.s : true
CustomClassLoader finalize called..CustomClassLoader@4e25154f // ClassLoader1 GCed
CustomClassLoader finalize called..CustomClassLoader@6d06d69c // ClassLoader2 GCed
Identity hashCode of mainMethod's s : 1118140819 // Value - 3 ..

(1,2)和3的IdentityHashCode不同。因此,在“main”方法中使用的字符串“abc”与Test被2个不同的类加载器加载时添加到字符串常量池中的相同字符串字面值“abc”不同。

这听起来不错,但我想挑刺:我们能确定如果Test.s没有被垃圾回收,main中的s会有相同的identityHashCode吗?有没有办法检测特定对象是否已被垃圾回收?(无论如何都点赞) - PJTraill
@PJTraill - 如果Test.s没有被垃圾回收,那么main方法中的s是否会有相同的identityHashCode呢?请参考case-1。由于c1和c2的类加载器没有被GC,因此main方法中的"abc"、c1和c2具有相同的IdentityHashCode。 - TheLostMind
你能根据加载器排除哈希码吗?这也会产生相同的结果!毕竟,当TestStringLiteralGC被加载时,main中的s不是被创建了吗? - PJTraill
@PJTraill - 如果在类加载器被垃圾回收前在“main()”中创建了“s”,那么它将指向同一个“abc”。所有三者的IdentityHashCode都相同。 - TheLostMind

1

证明它的简单方法是使用 profiler。性能分析器可以让您查看应用程序中所有存在于内存中的对象。


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