是的,在 Java7 之后,如果加载字符串文字的类加载器被垃圾收集,并且没有对字符串文字的引用,则字符串文字可以被垃圾收集。
注意:在 Java -8 中,您必须调用 GC 两次,以确保 ClassLoaders 获得 GC(元空间..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
(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 ..
(1,2) 和 3 的 IdentityHashCodes 不同。所以,字符串“abc”
“main”方法中使用的字符串文字“abc”与之前的字符串文字“abc”不同
当测试被 2 个不同的加载时添加到字符串常量池
类加载器。