1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2017 SAP SE. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. 9 * 10 * This code is distributed in the hope that it will be useful, but WITHOUT 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 * version 2 for more details (a copy is included in the LICENSE file that 14 * accompanied this code). 15 * 16 * You should have received a copy of the GNU General Public License version 17 * 2 along with this work; if not, write to the Free Software Foundation, 18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 19 * 20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 21 * or visit www.oracle.com if you need additional information or have any 22 * questions. 23 */ 24 25 /** 26 * @test 27 * @bug 8173743 28 * @requires vm.compMode != "Xcomp" 29 * @summary Failures during class definition can lead to memory leaks in metaspace 30 * @requires vm.opt.final.ClassUnloading 31 * @library /test/lib 32 * @run main/othervm test.DefineClass defineClass 33 * @run main/othervm test.DefineClass defineSystemClass 34 * @run main/othervm -XX:+AllowParallelDefineClass 35 test.DefineClass defineClassParallel 36 * @run main/othervm -XX:-AllowParallelDefineClass 37 test.DefineClass defineClassParallel 38 * @run main/othervm -Djdk.attach.allowAttachSelf test.DefineClass redefineClass 39 * @run main/othervm -Djdk.attach.allowAttachSelf test.DefineClass redefineClassWithError 40 * @author volker.simonis@gmail.com 41 */ 42 43 package test; 44 45 import java.io.ByteArrayOutputStream; 46 import java.io.File; 47 import java.io.FileOutputStream; 48 import java.io.InputStream; 49 import java.lang.instrument.ClassDefinition; 50 import java.lang.instrument.Instrumentation; 51 import java.lang.management.ManagementFactory; 52 import java.util.Scanner; 53 import java.util.concurrent.CountDownLatch; 54 import java.util.jar.Attributes; 55 import java.util.jar.JarEntry; 56 import java.util.jar.JarOutputStream; 57 import java.util.jar.Manifest; 58 59 import javax.management.MBeanServer; 60 import javax.management.ObjectName; 61 62 import com.sun.tools.attach.VirtualMachine; 63 64 import jdk.test.lib.process.ProcessTools; 65 66 public class DefineClass { 67 68 private static Instrumentation instrumentation; 69 70 public void getID(CountDownLatch start, CountDownLatch stop) { 71 String id = "AAAAAAAA"; 72 System.out.println(id); 73 try { 74 // Signal that we've entered the activation.. 75 start.countDown(); 76 //..and wait until we can leave it. 77 stop.await(); 78 } catch (InterruptedException e) { 79 e.printStackTrace(); 80 } 81 System.out.println(id); 82 return; 83 } 84 85 private static class MyThread extends Thread { 86 private DefineClass dc; 87 private CountDownLatch start, stop; 88 89 public MyThread(DefineClass dc, CountDownLatch start, CountDownLatch stop) { 90 this.dc = dc; 91 this.start = start; 92 this.stop = stop; 93 } 94 95 public void run() { 96 dc.getID(start, stop); 97 } 98 } 99 100 private static class ParallelLoadingThread extends Thread { 101 private MyParallelClassLoader pcl; 102 private CountDownLatch stop; 103 private byte[] buf; 104 105 public ParallelLoadingThread(MyParallelClassLoader pcl, byte[] buf, CountDownLatch stop) { 106 this.pcl = pcl; 107 this.stop = stop; 108 this.buf = buf; 109 } 110 111 public void run() { 112 try { 113 stop.await(); 114 } catch (InterruptedException e) { 115 e.printStackTrace(); 116 } 117 try { 118 @SuppressWarnings("unchecked") 119 Class<DefineClass> dc = (Class<DefineClass>) pcl.myDefineClass(DefineClass.class.getName(), buf, 0, buf.length); 120 } 121 catch (LinkageError jle) { 122 // Expected with a parallel capable class loader and 123 // -XX:+AllowParallelDefineClass 124 pcl.incrementLinkageErrors(); 125 } 126 127 } 128 } 129 130 static private class MyClassLoader extends ClassLoader { 131 public Class<?> myDefineClass(String name, byte[] b, int off, int len) throws ClassFormatError { 132 return defineClass(name, b, off, len, null); 133 } 134 } 135 136 static private class MyParallelClassLoader extends ClassLoader { 137 static { 138 System.out.println("parallelCapable : " + registerAsParallelCapable()); 139 } 140 public Class<?> myDefineClass(String name, byte[] b, int off, int len) throws ClassFormatError { 141 return defineClass(name, b, off, len, null); 142 } 143 public synchronized void incrementLinkageErrors() { 144 linkageErrors++; 145 } 146 public int getLinkageErrors() { 147 return linkageErrors; 148 } 149 private volatile int linkageErrors; 150 } 151 152 public static void agentmain(String args, Instrumentation inst) { 153 System.out.println("Loading Java Agent."); 154 instrumentation = inst; 155 } 156 157 158 private static void loadInstrumentationAgent(String myName, byte[] buf) throws Exception { 159 // Create agent jar file on the fly 160 Manifest m = new Manifest(); 161 m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); 162 m.getMainAttributes().put(new Attributes.Name("Agent-Class"), myName); 163 m.getMainAttributes().put(new Attributes.Name("Can-Redefine-Classes"), "true"); 164 File jarFile = File.createTempFile("agent", ".jar"); 165 jarFile.deleteOnExit(); 166 JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile), m); 167 jar.putNextEntry(new JarEntry(myName.replace('.', '/') + ".class")); 168 jar.write(buf); 169 jar.close(); 170 String pid = Long.toString(ProcessTools.getProcessId()); 171 System.out.println("Our pid is = " + pid); 172 VirtualMachine vm = VirtualMachine.attach(pid); 173 vm.loadAgent(jarFile.getAbsolutePath()); 174 } 175 176 private static byte[] getBytecodes(String myName) throws Exception { 177 InputStream is = DefineClass.class.getResourceAsStream(myName + ".class"); 178 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 179 byte[] buf = new byte[4096]; 180 int len; 181 while ((len = is.read(buf)) != -1) baos.write(buf, 0, len); 182 buf = baos.toByteArray(); 183 System.out.println("sizeof(" + myName + ".class) == " + buf.length); 184 return buf; 185 } 186 187 private static int getStringIndex(String needle, byte[] buf) { 188 return getStringIndex(needle, buf, 0); 189 } 190 191 private static int getStringIndex(String needle, byte[] buf, int offset) { 192 outer: 193 for (int i = offset; i < buf.length - offset - needle.length(); i++) { 194 for (int j = 0; j < needle.length(); j++) { 195 if (buf[i + j] != (byte)needle.charAt(j)) continue outer; 196 } 197 return i; 198 } 199 return 0; 200 } 201 202 private static void replaceString(byte[] buf, String name, int index) { 203 for (int i = index; i < index + name.length(); i++) { 204 buf[i] = (byte)name.charAt(i - index); 205 } 206 } 207 208 private static MBeanServer mbserver = ManagementFactory.getPlatformMBeanServer(); 209 210 private static int getClassStats(String pattern) { 211 try { 212 ObjectName diagCmd = new ObjectName("com.sun.management:type=DiagnosticCommand"); 213 214 String result = (String)mbserver.invoke(diagCmd , "gcClassStats" , new Object[] { null }, new String[] {String[].class.getName()}); 215 int count = 0; 216 try (Scanner s = new Scanner(result)) { 217 if (s.hasNextLine()) { 218 System.out.println(s.nextLine()); 219 } 220 while (s.hasNextLine()) { 221 String l = s.nextLine(); 222 if (l.endsWith(pattern)) { 223 count++; 224 System.out.println(l); 225 } 226 } 227 } 228 return count; 229 } 230 catch (Exception e) { 231 throw new RuntimeException("Test failed because we can't read the class statistics!", e); 232 } 233 } 234 235 private static void printClassStats(int expectedCount, boolean reportError) { 236 int count = getClassStats("DefineClass"); 237 String res = "Should have " + expectedCount + 238 " DefineClass instances and we have: " + count; 239 System.out.println(res); 240 if (reportError && count != expectedCount) { 241 throw new RuntimeException(res); 242 } 243 } 244 245 public static final int ITERATIONS = 10; 246 247 public static void main(String[] args) throws Exception { 248 String myName = DefineClass.class.getName(); 249 byte[] buf = getBytecodes(myName.substring(myName.lastIndexOf(".") + 1)); 250 int iterations = (args.length > 1 ? Integer.parseInt(args[1]) : ITERATIONS); 251 252 if (args.length == 0 || "defineClass".equals(args[0])) { 253 MyClassLoader cl = new MyClassLoader(); 254 for (int i = 0; i < iterations; i++) { 255 try { 256 @SuppressWarnings("unchecked") 257 Class<DefineClass> dc = (Class<DefineClass>) cl.myDefineClass(myName, buf, 0, buf.length); 258 System.out.println(dc); 259 } 260 catch (LinkageError jle) { 261 // Can only define once! 262 if (i == 0) throw new Exception("Should succeed the first time."); 263 } 264 } 265 // We expect to have two instances of DefineClass here: the initial version in which we are 266 // executing and another version which was loaded into our own classloader 'MyClassLoader'. 267 // All the subsequent attempts to reload DefineClass into our 'MyClassLoader' should have failed. 268 printClassStats(2, false); 269 System.gc(); 270 System.out.println("System.gc()"); 271 // At least after System.gc() the failed loading attempts should leave no instances around! 272 printClassStats(2, true); 273 } 274 else if ("defineSystemClass".equals(args[0])) { 275 MyClassLoader cl = new MyClassLoader(); 276 int index = getStringIndex("test/DefineClass", buf); 277 replaceString(buf, "java/DefineClass", index); 278 while ((index = getStringIndex("Ltest/DefineClass;", buf, index + 1)) != 0) { 279 replaceString(buf, "Ljava/DefineClass;", index); 280 } 281 index = getStringIndex("test.DefineClass", buf); 282 replaceString(buf, "java.DefineClass", index); 283 284 for (int i = 0; i < iterations; i++) { 285 try { 286 @SuppressWarnings("unchecked") 287 Class<DefineClass> dc = (Class<DefineClass>) cl.myDefineClass(null, buf, 0, buf.length); 288 throw new RuntimeException("Defining a class in the 'java' package should fail!"); 289 } 290 catch (java.lang.SecurityException jlse) { 291 // Expected, because we're not allowed to define a class in the 'java' package 292 } 293 } 294 // We expect to stay with one (the initial) instances of DefineClass. 295 // All the subsequent attempts to reload DefineClass into the 'java' package should have failed. 296 printClassStats(1, false); 297 System.gc(); 298 System.out.println("System.gc()"); 299 // At least after System.gc() the failed loading attempts should leave no instances around! 300 printClassStats(1, true); 301 } 302 else if ("defineClassParallel".equals(args[0])) { 303 MyParallelClassLoader pcl = new MyParallelClassLoader(); 304 CountDownLatch stop = new CountDownLatch(1); 305 306 Thread[] threads = new Thread[iterations]; 307 for (int i = 0; i < iterations; i++) { 308 (threads[i] = new ParallelLoadingThread(pcl, buf, stop)).start(); 309 } 310 stop.countDown(); // start parallel class loading.. 311 // ..and wait until all threads loaded the class 312 for (int i = 0; i < iterations; i++) { 313 threads[i].join(); 314 } 315 System.out.print("Counted " + pcl.getLinkageErrors() + " LinkageErrors "); 316 System.out.println(pcl.getLinkageErrors() == 0 ? 317 "" : "(use -XX:+AllowParallelDefineClass to avoid this)"); 318 System.gc(); 319 System.out.println("System.gc()"); 320 // After System.gc() we expect to remain with two instances: one is the initial version which is 321 // kept alive by this main method and another one in the parallel class loader. 322 printClassStats(2, true); 323 } 324 else if ("redefineClass".equals(args[0])) { 325 loadInstrumentationAgent(myName, buf); 326 int index = getStringIndex("AAAAAAAA", buf); 327 CountDownLatch stop = new CountDownLatch(1); 328 329 Thread[] threads = new Thread[iterations]; 330 for (int i = 0; i < iterations; i++) { 331 buf[index] = (byte) ('A' + i + 1); // Change string constant in getID() which is legal in redefinition 332 instrumentation.redefineClasses(new ClassDefinition(DefineClass.class, buf)); 333 DefineClass dc = DefineClass.class.newInstance(); 334 CountDownLatch start = new CountDownLatch(1); 335 (threads[i] = new MyThread(dc, start, stop)).start(); 336 start.await(); // Wait until the new thread entered the getID() method 337 } 338 // We expect to have one instance for each redefinition because they are all kept alive by an activation 339 // plus the initial version which is kept active by this main method. 340 printClassStats(iterations + 1, false); 341 stop.countDown(); // Let all threads leave the DefineClass.getID() activation.. 342 // ..and wait until really all of them returned from DefineClass.getID() 343 for (int i = 0; i < iterations; i++) { 344 threads[i].join(); 345 } 346 System.gc(); 347 System.out.println("System.gc()"); 348 // After System.gc() we expect to remain with two instances: one is the initial version which is 349 // kept alive by this main method and another one which is the latest redefined version. 350 printClassStats(2, true); 351 } 352 else if ("redefineClassWithError".equals(args[0])) { 353 loadInstrumentationAgent(myName, buf); 354 int index = getStringIndex("getID", buf); 355 356 for (int i = 0; i < iterations; i++) { 357 buf[index] = (byte) 'X'; // Change getID() to XetID() which is illegal in redefinition 358 try { 359 instrumentation.redefineClasses(new ClassDefinition(DefineClass.class, buf)); 360 throw new RuntimeException("Class redefinition isn't allowed to change method names!"); 361 } 362 catch (UnsupportedOperationException uoe) { 363 // Expected because redefinition can't change the name of methods 364 } 365 } 366 // We expect just a single DefineClass instance because failed redefinitions should 367 // leave no garbage around. 368 printClassStats(1, false); 369 System.gc(); 370 System.out.println("System.gc()"); 371 // At least after a System.gc() we should definitely stay with a single instance! 372 printClassStats(1, true); 373 } 374 } 375 } --- EOF ---