OSDN Git Service

Enable class unloading
authorMathieu Chartier <mathieuc@google.com>
Sat, 19 Sep 2015 19:44:38 +0000 (12:44 -0700)
committerMathieu Chartier <mathieuc@google.com>
Thu, 24 Sep 2015 23:57:34 +0000 (16:57 -0700)
Also added class unloading test. Added a missing write barrier in
the class linker to fix a heap corruption error.

Bug: 22720414
Change-Id: Iff615d69b574a4438e91d4c844279d202f4f2736

runtime/class_linker.cc
test/141-class-unload/expected.txt [new file with mode: 0644]
test/141-class-unload/info.txt [new file with mode: 0644]
test/141-class-unload/src-ex/IntHolder.java [new file with mode: 0644]
test/141-class-unload/src/Main.java [new file with mode: 0644]

index 6b9c8aa..15f8f0b 100644 (file)
@@ -1316,13 +1316,6 @@ void ClassLinker::VisitClassRoots(RootVisitor* visitor, VisitRootFlags flags) {
     // Need to make sure to not copy ArtMethods without doing read barriers since the roots are
     // marked concurrently and we don't hold the classlinker_classes_lock_ when we do the copy.
     boot_class_table_.VisitRoots(buffered_visitor);
-    // TODO: Avoid marking these to enable class unloading.
-    JavaVMExt* const vm = Runtime::Current()->GetJavaVM();
-    for (const ClassLoaderData& data : class_loaders_) {
-      mirror::Object* class_loader = vm->DecodeWeakGlobal(self, data.weak_root);
-      // Don't need to update anything since the class loaders will be updated by SweepSystemWeaks.
-      visitor->VisitRootIfNonNull(&class_loader, RootInfo(kRootVMInternal));
-    }
   } else if ((flags & kVisitRootFlagNewRoots) != 0) {
     for (auto& root : new_class_roots_) {
       mirror::Class* old_ref = root.Read<kWithoutReadBarrier>();
@@ -4266,6 +4259,11 @@ bool ClassLinker::LinkClass(Thread* self,
       ClassTable* const table = InsertClassTableForClassLoader(class_loader);
       mirror::Class* existing = table->UpdateClass(descriptor, h_new_class.Get(),
                                                    ComputeModifiedUtf8Hash(descriptor));
+      if (class_loader != nullptr) {
+        // We updated the class in the class table, perform the write barrier so that the GC knows
+        // about the change.
+        Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader);
+      }
       CHECK_EQ(existing, klass.Get());
       if (kIsDebugBuild && class_loader == nullptr && dex_cache_image_class_lookup_required_) {
         // Check a class loaded with the system class loader matches one in the image if the class
diff --git a/test/141-class-unload/expected.txt b/test/141-class-unload/expected.txt
new file mode 100644 (file)
index 0000000..be2671e
--- /dev/null
@@ -0,0 +1,9 @@
+1
+2
+1
+2
+null
+null
+null
+loader null false
+loader null false
diff --git a/test/141-class-unload/info.txt b/test/141-class-unload/info.txt
new file mode 100644 (file)
index 0000000..d8dd381
--- /dev/null
@@ -0,0 +1 @@
+Test that classes get freed after they are no longer reachable.
diff --git a/test/141-class-unload/src-ex/IntHolder.java b/test/141-class-unload/src-ex/IntHolder.java
new file mode 100644 (file)
index 0000000..0a1c1e6
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Simple class that holds a static int for testing that class unloading works
+// and re-runs the class initializer.
+public class IntHolder {
+    private static int value = 1;
+
+    public static void setValue(int newValue) {
+        value = newValue;
+    }
+
+    public static int getValue() {
+        return value;
+    }
+
+    public static void runGC() {
+        Runtime.getRuntime().gc();
+    }
+}
diff --git a/test/141-class-unload/src/Main.java b/test/141-class-unload/src/Main.java
new file mode 100644 (file)
index 0000000..3a2ac9b
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class Main {
+    static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar";
+
+    public static void main(String[] args) throws Exception {
+        Class pathClassLoader = Class.forName("dalvik.system.PathClassLoader");
+        if (pathClassLoader == null) {
+            throw new AssertionError("Couldn't find path class loader class");
+        }
+        Constructor constructor =
+            pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class);
+        try {
+            testUnloadClassAndLoader(constructor);
+            // Test that we don't unload if we have a Method keeping the class live.
+            testNoUnloadInvoke(constructor);
+            // Test that we don't unload if we have an instance.
+            testNoUnloadInstance(constructor);
+            // Stress test to make sure we dont leak memory.
+            stressTest(constructor);
+        } catch (Exception e) {
+            System.out.println(e);
+        }
+    }
+
+    private static void stressTest(Constructor constructor) throws Exception {
+        for (int i = 0; i <= 100; ++i) {
+            setUpUnloadLoader(constructor);
+            if (i % 10 == 0) {
+                Runtime.getRuntime().gc();
+            }
+        }
+    }
+
+    private static void testUnloadClassAndLoader(Constructor constructor) throws Exception {
+        WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor);
+        WeakReference<Class> klass = setUpUnloadClass(constructor);
+        // No strong refernces to class loader, should get unloaded.
+        Runtime.getRuntime().gc();
+        WeakReference<Class> klass2 = setUpUnloadClass(constructor);
+        Runtime.getRuntime().gc();
+        // If the weak reference is cleared, then it was unloaded.
+        System.out.println(klass.get());
+        System.out.println(klass2.get());
+        System.out.println(loader.get());
+    }
+
+    private static void testNoUnloadInvoke(Constructor constructor) throws Exception {
+        WeakReference<ClassLoader> loader =
+            new WeakReference((ClassLoader) constructor.newInstance(
+                DEX_FILE, ClassLoader.getSystemClassLoader()));
+        WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder"));
+        intHolder.get().getDeclaredMethod("runGC").invoke(intHolder.get());
+        boolean isNull = loader.get() == null;
+        System.out.println("loader null " + isNull);
+    }
+
+    private static void testNoUnloadInstance(Constructor constructor) throws Exception {
+        WeakReference<ClassLoader> loader =
+            new WeakReference((ClassLoader) constructor.newInstance(
+                DEX_FILE, ClassLoader.getSystemClassLoader()));
+        WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder"));
+        Object o = intHolder.get().newInstance();
+        Runtime.getRuntime().gc();
+        boolean isNull = loader.get() == null;
+        System.out.println("loader null " + isNull);
+    }
+
+    private static WeakReference<Class> setUpUnloadClass(Constructor constructor)
+        throws Exception {
+        ClassLoader loader = (ClassLoader) constructor.newInstance(
+            DEX_FILE, ClassLoader.getSystemClassLoader());
+        Class intHolder = loader.loadClass("IntHolder");
+        Method getValue = intHolder.getDeclaredMethod("getValue");
+        Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
+        // Make sure we don't accidentally preserve the value in the int holder, the class
+        // initializer should be re-run.
+        System.out.println((int) getValue.invoke(intHolder));
+        setValue.invoke(intHolder, 2);
+        System.out.println((int) getValue.invoke(intHolder));
+        return new WeakReference(intHolder);
+    }
+
+    private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor constructor)
+        throws Exception {
+        ClassLoader loader = (ClassLoader) constructor.newInstance(
+            DEX_FILE, ClassLoader.getSystemClassLoader());
+        Class intHolder = loader.loadClass("IntHolder");
+        Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
+        setValue.invoke(intHolder, 2);
+        return new WeakReference(loader);
+    }
+
+}