简体   繁体   English

java字符串文字可以被垃圾收集吗?如果是,如何证明?

[英]Can java string literals be garbage collected?. If Yes, how to prove it?

Can java String literals like "abc" be garbage collected?. "abc"这样的java 字符串文字可以被垃圾收集吗? If yes, how can we programatially prove that they are GCed? 如果是,我们如何以编程方式证明他们是GCed?

Yes, post Java7, String literals can be garbage collected if the class loader which loaded it gets garbage collected and there are no references to the string literal. 是的,发布Java7,如果加载它的类加载器被垃圾收集并且没有对字符串文字的引用,则可以对字符串文字进行垃圾回收。

Note : In Java -8, you will have to call GC twice in order to ensure that ClassLoaders get GCed (Metaspace.. pfff..Using a different GC won't help). 注意:在Java -8中,您必须调用GC两次以确保ClassLoaders获得GCed(Metaspace .. pfff ..使用不同的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

Same IdentityHashCode for (1,2) and 3 means the same string literal "abc" is being used in all 3 places. (1,2)和3的相同IdentityHashCode表示在所有3个位置使用相同的字符串文字“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 ..

IdentityHashCodes for (1,2) and 3 are different. (1,2)和3的IdentityHashCodes是不同的。 So, the string "abc" used in "main" method is not the same string literal "abc" which was added to string constants pool when Test was loaded by 2 different classloaders. 因此,“main”方法中使用的字符串“abc”与由2个不同的类加载器加载Test时添加到字符串常量池的字符串文字“abc”不同。

An easy way to proof it, is using a profiler . 一种简单的证明方法是使用分析器 A profiler gives you the ability to see all objects that are in memory of the application. 通过分析器,您可以查看应用程序内存中的所有对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM