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