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

266 views Asked by At

Can java String literals like "abc" be garbage collected?. If yes, how can we programatially prove that they are GCed?

2

There are 2 answers

4
TheLostMind On

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.

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).

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.

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. 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.

0
bvdb On

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.