Path: blob/master/test/hotspot/jtreg/runtime/Metaspace/DefineClass.java
40942 views
/*1* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.2* Copyright (c) 2017 SAP SE. 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 it6* under the terms of the GNU General Public License version 2 only, as7* published by the Free Software Foundation.8*9* This code is distributed in the hope that it will be useful, but WITHOUT10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License12* version 2 for more details (a copy is included in the LICENSE file that13* accompanied this code).14*15* You should have received a copy of the GNU General Public License version16* 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 USA20* or visit www.oracle.com if you need additional information or have any21* questions.22*/2324/**25* @test26* @bug 817374327* @requires vm.compMode != "Xcomp"28* @requires vm.jvmti29* @summary Failures during class definition can lead to memory leaks in metaspace30* @requires vm.opt.final.ClassUnloading31* @library /test/lib32* @build sun.hotspot.WhiteBox33* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox34* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI test.DefineClass defineClass35* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI test.DefineClass defineSystemClass36* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI37* -XX:+AllowParallelDefineClass38* test.DefineClass defineClassParallel39* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI40* -XX:-AllowParallelDefineClass41* test.DefineClass defineClassParallel42* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI43* -Djdk.attach.allowAttachSelf test.DefineClass redefineClass44* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI45* -Djdk.attach.allowAttachSelf test.DefineClass redefineClassWithError46* @author [email protected]47*/4849package test;5051import java.io.ByteArrayOutputStream;52import java.io.File;53import java.io.FileOutputStream;54import java.io.InputStream;55import java.lang.instrument.ClassDefinition;56import java.lang.instrument.Instrumentation;57import java.util.concurrent.CountDownLatch;58import java.util.jar.Attributes;59import java.util.jar.JarEntry;60import java.util.jar.JarOutputStream;61import java.util.jar.Manifest;6263import com.sun.tools.attach.VirtualMachine;6465import jdk.test.lib.process.ProcessTools;66import sun.hotspot.WhiteBox;6768public class DefineClass {6970private static Instrumentation instrumentation;7172public void getID(CountDownLatch start, CountDownLatch stop) {73String id = "AAAAAAAA";74System.out.println(id);75try {76// Signal that we've entered the activation..77start.countDown();78//..and wait until we can leave it.79stop.await();80} catch (InterruptedException e) {81e.printStackTrace();82}83System.out.println(id);84return;85}8687private static class MyThread extends Thread {88private DefineClass dc;89private CountDownLatch start, stop;9091public MyThread(DefineClass dc, CountDownLatch start, CountDownLatch stop) {92this.dc = dc;93this.start = start;94this.stop = stop;95}9697public void run() {98dc.getID(start, stop);99}100}101102private static class ParallelLoadingThread extends Thread {103private MyParallelClassLoader pcl;104private CountDownLatch stop;105private byte[] buf;106107public ParallelLoadingThread(MyParallelClassLoader pcl, byte[] buf, CountDownLatch stop) {108this.pcl = pcl;109this.stop = stop;110this.buf = buf;111}112113public void run() {114try {115stop.await();116} catch (InterruptedException e) {117e.printStackTrace();118}119try {120@SuppressWarnings("unchecked")121Class<DefineClass> dc = (Class<DefineClass>) pcl.myDefineClass(DefineClass.class.getName(), buf, 0, buf.length);122}123catch (LinkageError jle) {124// Expected with a parallel capable class loader and125// -XX:+AllowParallelDefineClass126pcl.incrementLinkageErrors();127}128129}130}131132static private class MyClassLoader extends ClassLoader {133public Class<?> myDefineClass(String name, byte[] b, int off, int len) throws ClassFormatError {134return defineClass(name, b, off, len, null);135}136}137138static private class MyParallelClassLoader extends ClassLoader {139static {140System.out.println("parallelCapable : " + registerAsParallelCapable());141}142public Class<?> myDefineClass(String name, byte[] b, int off, int len) throws ClassFormatError {143return defineClass(name, b, off, len, null);144}145public synchronized void incrementLinkageErrors() {146linkageErrors++;147}148public int getLinkageErrors() {149return linkageErrors;150}151private volatile int linkageErrors;152}153154public static void agentmain(String args, Instrumentation inst) {155System.out.println("Loading Java Agent.");156instrumentation = inst;157}158159160private static void loadInstrumentationAgent(String myName, byte[] buf) throws Exception {161// Create agent jar file on the fly162Manifest m = new Manifest();163m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");164m.getMainAttributes().put(new Attributes.Name("Agent-Class"), myName);165m.getMainAttributes().put(new Attributes.Name("Can-Redefine-Classes"), "true");166File jarFile = File.createTempFile("agent", ".jar");167jarFile.deleteOnExit();168JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile), m);169jar.putNextEntry(new JarEntry(myName.replace('.', '/') + ".class"));170jar.write(buf);171jar.close();172String pid = Long.toString(ProcessTools.getProcessId());173System.out.println("Our pid is = " + pid);174VirtualMachine vm = VirtualMachine.attach(pid);175vm.loadAgent(jarFile.getAbsolutePath());176}177178private static byte[] getBytecodes(String myName) throws Exception {179InputStream is = DefineClass.class.getResourceAsStream(myName + ".class");180ByteArrayOutputStream baos = new ByteArrayOutputStream();181byte[] buf = new byte[4096];182int len;183while ((len = is.read(buf)) != -1) baos.write(buf, 0, len);184buf = baos.toByteArray();185System.out.println("sizeof(" + myName + ".class) == " + buf.length);186return buf;187}188189private static int getStringIndex(String needle, byte[] buf) {190return getStringIndex(needle, buf, 0);191}192193private static int getStringIndex(String needle, byte[] buf, int offset) {194outer:195for (int i = offset; i < buf.length - offset - needle.length(); i++) {196for (int j = 0; j < needle.length(); j++) {197if (buf[i + j] != (byte)needle.charAt(j)) continue outer;198}199return i;200}201return 0;202}203204private static void replaceString(byte[] buf, String name, int index) {205for (int i = index; i < index + name.length(); i++) {206buf[i] = (byte)name.charAt(i - index);207}208}209210public static WhiteBox wb = WhiteBox.getWhiteBox();211212private static void checkClasses(int expectedCount, boolean reportError) {213int count = wb.countAliveClasses("test.DefineClass");214String res = "Should have " + expectedCount +215" DefineClass instances and we have: " + count;216System.out.println(res);217if (reportError && count != expectedCount) {218throw new RuntimeException(res);219}220}221222public static final int ITERATIONS = 10;223224private static void checkClassesAfterGC(int expectedCount) {225// The first System.gc() doesn't clean metaspaces but triggers cleaning226// for the next safepoint.227// In the future the ServiceThread may clean metaspaces, but this loop228// should give it enough time to run, when that is changed.229// We might need to revisit this test though.230for (int i = 0; i < ITERATIONS; i++) {231System.gc();232System.out.println("System.gc()");233// Break if the GC has cleaned metaspace before iterations.234if (wb.countAliveClasses("test.DefineClass") == expectedCount) break;235}236checkClasses(expectedCount, true);237}238239public static void main(String[] args) throws Exception {240String myName = DefineClass.class.getName();241byte[] buf = getBytecodes(myName.substring(myName.lastIndexOf(".") + 1));242int iterations = (args.length > 1 ? Integer.parseInt(args[1]) : ITERATIONS);243244if (args.length == 0 || "defineClass".equals(args[0])) {245MyClassLoader cl = new MyClassLoader();246for (int i = 0; i < iterations; i++) {247try {248@SuppressWarnings("unchecked")249Class<DefineClass> dc = (Class<DefineClass>) cl.myDefineClass(myName, buf, 0, buf.length);250System.out.println(dc);251}252catch (LinkageError jle) {253// Can only define once!254if (i == 0) throw new Exception("Should succeed the first time.");255}256}257// We expect to have two instances of DefineClass here: the initial version in which we are258// executing and another version which was loaded into our own classloader 'MyClassLoader'.259// All the subsequent attempts to reload DefineClass into our 'MyClassLoader' should have failed.260// The ClassLoaderDataGraph has the failed instances recorded at least until the next GC.261checkClasses(2, false);262// At least after some System.gc() the failed loading attempts should leave no instances around!263checkClassesAfterGC(2);264}265else if ("defineSystemClass".equals(args[0])) {266MyClassLoader cl = new MyClassLoader();267int index = getStringIndex("test/DefineClass", buf);268replaceString(buf, "java/DefineClass", index);269while ((index = getStringIndex("Ltest/DefineClass;", buf, index + 1)) != 0) {270replaceString(buf, "Ljava/DefineClass;", index);271}272index = getStringIndex("test.DefineClass", buf);273replaceString(buf, "java.DefineClass", index);274275for (int i = 0; i < iterations; i++) {276try {277@SuppressWarnings("unchecked")278Class<DefineClass> dc = (Class<DefineClass>) cl.myDefineClass(null, buf, 0, buf.length);279throw new RuntimeException("Defining a class in the 'java' package should fail!");280}281catch (java.lang.SecurityException jlse) {282// Expected, because we're not allowed to define a class in the 'java' package283}284}285// We expect to stay with one (the initial) instances of DefineClass.286// All the subsequent attempts to reload DefineClass into the 'java' package should have failed.287// The ClassLoaderDataGraph has the failed instances recorded at least until the next GC.288checkClasses(1, false);289checkClassesAfterGC(1);290}291else if ("defineClassParallel".equals(args[0])) {292MyParallelClassLoader pcl = new MyParallelClassLoader();293CountDownLatch stop = new CountDownLatch(1);294295Thread[] threads = new Thread[iterations];296for (int i = 0; i < iterations; i++) {297(threads[i] = new ParallelLoadingThread(pcl, buf, stop)).start();298}299stop.countDown(); // start parallel class loading..300// ..and wait until all threads loaded the class301for (int i = 0; i < iterations; i++) {302threads[i].join();303}304System.out.print("Counted " + pcl.getLinkageErrors() + " LinkageErrors ");305System.out.println(pcl.getLinkageErrors() == 0 ?306"" : "(use -XX:+AllowParallelDefineClass to avoid this)");307// After System.gc() we expect to remain with two instances: one is the initial version which is308// kept alive by this main method and another one in the parallel class loader.309checkClassesAfterGC(2);310}311else if ("redefineClass".equals(args[0])) {312loadInstrumentationAgent(myName, buf);313int index = getStringIndex("AAAAAAAA", buf);314CountDownLatch stop = new CountDownLatch(1);315316Thread[] threads = new Thread[iterations];317for (int i = 0; i < iterations; i++) {318buf[index] = (byte) ('A' + i + 1); // Change string constant in getID() which is legal in redefinition319instrumentation.redefineClasses(new ClassDefinition(DefineClass.class, buf));320DefineClass dc = DefineClass.class.newInstance();321CountDownLatch start = new CountDownLatch(1);322(threads[i] = new MyThread(dc, start, stop)).start();323start.await(); // Wait until the new thread entered the getID() method324}325// We expect to have one instance for each redefinition because they are all kept alive by an activation326// plus the initial version which is kept active by this main method.327checkClasses(iterations + 1, true);328stop.countDown(); // Let all threads leave the DefineClass.getID() activation..329// ..and wait until really all of them returned from DefineClass.getID()330for (int i = 0; i < iterations; i++) {331threads[i].join();332}333// After System.gc() we expect to remain with two instances: one is the initial version which is334// kept alive by this main method and another one which is the latest redefined version.335checkClassesAfterGC(2);336}337else if ("redefineClassWithError".equals(args[0])) {338loadInstrumentationAgent(myName, buf);339int index = getStringIndex("getID", buf);340341for (int i = 0; i < iterations; i++) {342buf[index] = (byte) 'X'; // Change getID() to XetID() which is illegal in redefinition343try {344instrumentation.redefineClasses(new ClassDefinition(DefineClass.class, buf));345throw new RuntimeException("Class redefinition isn't allowed to change method names!");346}347catch (UnsupportedOperationException uoe) {348// Expected because redefinition can't change the name of methods349}350}351// We expect just a single DefineClass instance because failed redefinitions should352// leave no garbage around.353// The ClassLoaderDataGraph has the failed instances recorded at least until the next GC.354checkClasses(1, false);355// At least after a System.gc() we should definitely stay with a single instance!356checkClassesAfterGC(1);357}358}359}360361362