Can java string literals be garbage collected ?. If so, how to prove it?
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 to share