1 /*
   2  * Copyright (c) 2018, Red Hat, Inc. All rights reserved.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.
   7  *
   8  * This code is distributed in the hope that it will be useful, but WITHOUT
   9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11  * version 2 for more details (a copy is included in the LICENSE file that
  12  * accompanied this code).
  13  *
  14  * You should have received a copy of the GNU General Public License version
  15  * 2 along with this work; if not, write to the Free Software Foundation,
  16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  17  *
  18  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  19  * or visit www.oracle.com if you need additional information or have any
  20  * questions.
  21  *
  22  */
  23 
  24 /**
  25  * @test TestClassLoaderLeak
  26  * @summary Test OOME in due to classloader leak
  27  * @key gc
  28  * @library /testlibrary
  29  *
  30  * @run main/timeout=480 TestClassLoaderLeak
  31  */
  32 
  33 import java.util.*;
  34 import java.io.*;
  35 import java.nio.*;
  36 import java.nio.file.*;
  37 import com.oracle.java.testlibrary.*;
  38 
  39 public class TestClassLoaderLeak {
  40 
  41     static final int SIZE = 1 * 1024 * 1024;
  42     static final int COUNT = 128;
  43 
  44     static volatile Object sink;
  45 
  46     static class Dummy {
  47         static final int[] PAYLOAD = new int[SIZE];
  48     }
  49 
  50     static class MyClassLoader extends ClassLoader {
  51         final String path;
  52 
  53         MyClassLoader(String path) {
  54             this.path = path;
  55         }
  56 
  57         public Class<?> loadClass(String name) throws ClassNotFoundException {
  58             try {
  59                 File f = new File(path, name + ".class");
  60                 if (!f.exists()) {
  61                     return super.loadClass(name);
  62                 }
  63 
  64                 Path path = Paths.get(f.getAbsolutePath());
  65                 byte[] cls = Files.readAllBytes(path);
  66                 return defineClass(name, cls, 0, cls.length, null);
  67             } catch (IOException e) {
  68                 throw new ClassNotFoundException(name);
  69             }
  70         }
  71     }
  72 
  73     static void load(String path) throws Exception {
  74         ClassLoader cl = new MyClassLoader(path);
  75         Class<Dummy> c = (Class<Dummy>) Class.forName("TestClassLoaderLeak$Dummy", true, cl);
  76         if (c.getClassLoader() != cl) {
  77             throw new IllegalStateException("Should have loaded by target loader");
  78         }
  79         sink = c;
  80     }
  81 
  82     public static void passWith(String... args) throws Exception {
  83         testWith(true, args);
  84     }
  85 
  86     public static void failWith(String... args) throws Exception {
  87         testWith(false, args);
  88     }
  89 
  90     public static void testWith(boolean shouldPass, String... args) throws Exception {
  91         List<String> pbArgs = new ArrayList<>();
  92         pbArgs.add("-Xmx128m");
  93         pbArgs.add("-XX:+UnlockExperimentalVMOptions");
  94         pbArgs.add("-XX:+UnlockDiagnosticVMOptions");
  95         pbArgs.add("-XX:+UseShenandoahGC");
  96         pbArgs.addAll(Arrays.asList(args));
  97         pbArgs.add(TestClassLoaderLeak.class.getName());
  98         pbArgs.add("test");
  99 
 100         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(pbArgs.toArray(new String[0]));
 101 
 102         OutputAnalyzer analyzer = new OutputAnalyzer(pb.start());
 103 
 104         if (shouldPass) {
 105             analyzer.shouldHaveExitValue(0);
 106             analyzer.shouldNotContain("java.lang.OutOfMemoryError");
 107             analyzer.shouldContain("All good");
 108         } else {
 109             analyzer.shouldHaveExitValue(1);
 110             analyzer.shouldContain("java.lang.OutOfMemoryError");
 111             analyzer.shouldNotContain("All good");
 112         }
 113     }
 114 
 115     public static void main(String[] args) throws Exception {
 116         if (args.length > 0) {
 117             String classDir = TestClassLoaderLeak.class.getProtectionDomain().getCodeSource().getLocation().getPath();
 118             for (int c = 0; c < COUNT; c++) {
 119                 load(classDir);
 120             }
 121             System.out.println("All good");
 122             return;
 123         }
 124 
 125         String[][][] modeHeuristics = new String[][][] {
 126              {{"normal"},    {"adaptive", "compact", "static", "aggressive"}},
 127              {{"traversal"}, {"adaptive", "aggressive"}},
 128              {{"passive"},   {"passive"}}
 129         };
 130 
 131         for (String[][] mh : modeHeuristics) {
 132             String mode = mh[0][0];
 133             String[] heuristics = mh[1];
 134             for (String h : heuristics) {
 135                 // Forceful enabling should work
 136                 passWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:+ClassUnloading");
 137                 passWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:+ClassUnloadingWithConcurrentMark");
 138 
 139                 // Even when concurrent unloading is disabled, Full GC has to recover
 140                 passWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:+ClassUnloading", "-XX:-ClassUnloadingWithConcurrentMark");
 141                 passWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:+ClassUnloading", "-XX:-ClassUnloadingWithConcurrentMark", "-XX:ShenandoahUnloadClassesFrequency=0");
 142                 passWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:+ClassUnloading", "-XX:+ClassUnloadingWithConcurrentMark", "-XX:ShenandoahUnloadClassesFrequency=0");
 143 
 144                 // Should OOME when unloading forcefully disabled, even if local flags try to enable it back
 145                 failWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:-ClassUnloading");
 146                 failWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:-ClassUnloading", "-XX:+ClassUnloadingWithConcurrentMark");
 147                 failWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:-ClassUnloading", "-XX:+ClassUnloadingWithConcurrentMark", "-XX:ShenandoahUnloadClassesFrequency=1");
 148                 failWith("-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-XX:-ClassUnloading", "-XX:-ClassUnloadingWithConcurrentMark", "-XX:ShenandoahUnloadClassesFrequency=1");
 149             }
 150         }
 151     }
 152 }