Can java string literals be garbage collected ?. If so, how to prove it?

Can java string literals such as "abc"

be garbage collected ?. If so, how can we programmatically prove they are GCed?

+3


source to share


2 answers


Yes, after Java7, String literals can be garbage collected if the classloader that loaded it gets garbage and there are no references to the string literal.

Note. In Java -8, you will need to call GC twice to ensure the ClassLoaders get GCed (Metaspace .. pfff..Using another GC won't help).

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 : " + System.identityHashCode(s)); // Identity hashCode of mainMethod 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 : 1442407170 // Value -3

      



Same IdentityHashCode for (1,2) and 3 means the same string literal "abc" is used in all three places.

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 : " + System.identityHashCode(s)); // Identity hashCode of mainMethod 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 : 1118140819 // Value - 3 ..

      

IdentityHashCodes for (1,2) and 3 are different. So the "abc" string used in the "main" method is not the same string literal "abc" that was added to the string constant pool when Test was loaded by two different Class Loaders.

+6


source


An easy way to prove this is using profiler . The profiler gives you the ability to see all of the objects in the application's memory.



+1


source







All Articles