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

TheLostMind
- 35,966
- 12
- 68
- 104
-
schizophrenia? :) just kidding, good one :) – zubergu Jun 11 '15 at 10:03
-
@zubergu - Not really.. Just had some free time.. Thought of using it *judiciously* :) – TheLostMind Jun 11 '15 at 10:05
-
1let me look up judiciously... :D – zubergu Jun 11 '15 at 10:06
-
1possible duplicate of [Garbage collection of String literals](http://stackoverflow.com/questions/15324143/garbage-collection-of-string-literals) – weston Jun 11 '15 at 10:09
-
@weston - I have seen that question. I wanted to code / see it myself.. :) – TheLostMind Jun 11 '15 at 10:10
-
@weston - That question doesn't contain code to prove what is happening.. So, this isn't a dup – TheLostMind Jun 11 '15 at 10:11
2 Answers
6
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.

TheLostMind
- 35,966
- 12
- 68
- 104
-
This sounds good, but I’m trying to nit-pick: can we be sure that if `Test.s` had not been garbage collected, the `s` in `main` would have had the same identityHashCode? Is there any way of detecting that a specific object is garbage collected? (upvote anyway) – PJTraill Jun 11 '15 at 12:58
-
@PJTraill - *if Test.s had not been garbage collected, the s in main would have had the same identityHashCode?* - check case-1. Since c1 and c2's class loaders are not GCed. The `"abc"` of main and c1 and c2 have same IdentityHashCode – TheLostMind Jun 11 '15 at 13:01
-
Can you exclude the hash code depending on the Loader? — that would also produce these results! After all, is `s` in main not created when `TestStringLiteralGC` is loaded? – PJTraill Jun 11 '15 at 13:14
-
@PJTraill - If `s` in `main()` was created before the classloaders get GCed, then it will point to the same "abc". All 3 will have the same IdentityHashCode. – TheLostMind Jun 11 '15 at 13:22