1 /* 2 Copyright (c) 2010, 2021, Oracle and/or its affiliates. 3 All rights reserved. Use is subject to license terms. 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License, version 2.0, 7 as published by the Free Software Foundation. 8 9 This program is also distributed with certain software (including 10 but not limited to OpenSSL) that is licensed under separate terms, 11 as designated in a particular file or component or in included license 12 documentation. The authors of MySQL hereby grant you an additional 13 permission to link the program and your derivative works with the 14 separately licensed software that they have included with MySQL. 15 16 This program is distributed in the hope that it will be useful, 17 but WITHOUT ANY WARRANTY; without even the implied warranty of 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 GNU General Public License, version 2.0, for more details. 20 21 You should have received a copy of the GNU General Public License 22 along with this program; if not, write to the Free Software 23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 24 */ 25 /* 26 * MyLoadUnloadTest.java 27 */ 28 29 package test; 30 31 import java.lang.ref.WeakReference; 32 33 import java.lang.reflect.Method; 34 import java.lang.reflect.InvocationTargetException; 35 36 import java.io.PrintWriter; 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.IOException; 40 import java.io.FileNotFoundException; 41 42 import java.net.URL; 43 import java.net.URI; 44 import java.net.MalformedURLException; 45 import java.net.URISyntaxException; 46 import java.net.URLClassLoader; 47 48 import java.nio.ByteBuffer; 49 import java.nio.channels.FileChannel; 50 51 /** 52 * A test for loading, calling into, and unloading a native library. 53 * 54 * Under the JNI specification, a native library is loaded by the class loader 55 * of that class which called the method System.loadLibrary(String). In a 56 * Java VM, a library cannot be loaded into more than one class loader 57 * (since JDK 1.2, to preserve the separation of name-spaces). A library is 58 * unloaded when the class loader containing the library is garbage collected. 59 * 60 * Therefore, the task of loading of the native library has to be carried 61 * out by newly created class loader instances that are separate from the 62 * loader of this test class. This implies that this test class must not 63 * statically refer to the code calling into the native library, for this 64 * would load the native library by this class loader when running this test, 65 * and any subsequent attempt to load the library by another class loader 66 * instance would fail (with an UnsatisfiedLinkError). 67 * 68 * Hence, this class tests the loading and unloading of a native library by 69 * executing the following cycle at least twice: 70 * <ol> 71 * <li> create a test class loader instance, 72 * <li> using reflection, instruct the test class loader to load the a 73 * native library and run Java code calling into the native library, 74 * <li> clear any (strong) references to the test class loader 75 * <li> invoke the system's garbage collector to finalize the test class 76 * loader and unload the library. 77 * </ol> 78 */ 79 public class MyLoadUnloadTest { 80 81 /** 82 * The stream to write messages to. 83 */ 84 static protected final PrintWriter out = new PrintWriter(System.out, true); 85 86 /** 87 * The stream to write error messages to. 88 */ 89 static protected final PrintWriter err = new PrintWriter(System.err, true); 90 91 // ensure that asserts are enabled 92 static { 93 boolean assertsEnabled = false; 94 assert assertsEnabled = true; // intentional side effect 95 if (!assertsEnabled) { 96 throw new RuntimeException("Asserts must be enabled for this test to be effective!"); 97 } 98 } 99 100 static final String pprefixes_prop 101 = "com.mysql.jtie.test.MyLoadUnloadTest.target_package_prefixes"; 102 static final String cname_prop 103 = "com.mysql.jtie.test.MyLoadUnloadTest.target_class_name"; 104 static final String mname_prop 105 = "com.mysql.jtie.test.MyLoadUnloadTest.target_method_name"; 106 107 /** 108 * A weak reference to a class loader loading the native library. 109 */ 110 static protected WeakReference<ClassLoader> cl = null; 111 112 /** 113 * Shortcut to the Runtime. 114 */ 115 static private final Runtime rt = Runtime.getRuntime(); 116 117 /** 118 * A ClassLoader that loads classes from certain packages by itself 119 * not delegating loading of these to the parent class loader. 120 */ 121 // this loader is only used for debugging this test 122 static class SelfishFileClassLoader extends ClassLoader { 123 SelfishFileClassLoader()124 public SelfishFileClassLoader() { 125 out.println("--> SelfishFileClassLoader()"); 126 out.println(" this = " + this); 127 out.println(" getParent() = " + getParent()); 128 out.println("<-- SelfishFileClassLoader()"); 129 } 130 getClassData(String name)131 private ByteBuffer getClassData(String name) 132 throws ClassNotFoundException { 133 out.println("--> SelfishFileClassLoader.getClassData(String)"); 134 135 // for simplicity of code, only support loading classes from files; 136 // an alternative approach to collect all bytes using 137 // InputStream ClassLoader.getSystemResourceAsStream(String) 138 // ReadableByteChannel Channels.newChannel(InputStream) 139 // int ReadableByteChannel.read(ByteBuffer) 140 // requires multiple copying within nested loops, since the total 141 // number of bytes to be read is not known in advance. 142 143 String rname = name.replace('.', '/') + ".class"; 144 145 // locate the class data 146 final URL url = ClassLoader.getSystemResource(rname); 147 if (url == null) { 148 final String msg = ("no definition of the class " 149 + rname + " could be found."); 150 throw new ClassNotFoundException(msg); 151 } 152 out.println(" url = " + url); 153 154 // convert into a URI 155 final URI uri; 156 try { 157 uri = url.toURI(); 158 } catch (URISyntaxException ex) { 159 final String msg = ("the URL " + url + " is not formatted" 160 + "strictly according to RFC2396 and " 161 + "cannot be converted to a URI."); 162 throw new ClassNotFoundException(msg, ex); 163 } 164 165 // convert into a pathname 166 final File f; 167 try { 168 f = new File(uri); 169 } catch (IllegalArgumentException ex) { 170 final String msg = ("the system-dependent URI " + uri 171 + " cannot be converted into a pathname."); 172 throw new ClassNotFoundException(msg, ex); 173 } 174 175 // check accessibility 176 if (!f.canRead()) { 177 final String msg = ("cannot read file " + f); 178 throw new ClassNotFoundException(msg); 179 } 180 out.println(" can read file " + f); 181 final long nn = f.length(); 182 out.println(" f.length = " + nn); 183 184 // open a FileInputStream 185 final FileInputStream fis; 186 try { 187 fis = new FileInputStream(f); 188 } catch (FileNotFoundException ex) { 189 final String msg = ("the file " + f 190 + " does not exist, is a directory, or" 191 + " cannot be opened for reading."); 192 throw new ClassNotFoundException(msg, ex); 193 } 194 195 // get the stream's FileChannel 196 final FileChannel fc = fis.getChannel(); 197 assert (fc != null); 198 out.println(" fc = " + fc); 199 200 // allocate a ByteBuffer and read file's content 201 final ByteBuffer bb; 202 try { 203 // not worth mapping file as ByteBuffer 204 // final MappedByteBuffer mbb 205 // = fc.map(MapMode.READ_ONLY, 0, fc.size()); 206 // unclear performance gain but platform-dependent behaviour 207 208 // retrieve the file's size 209 final long n; 210 try { 211 n = fc.size(); 212 } catch (IOException ex) { 213 final String msg = ("cannot get size of file " + f); 214 throw new ClassNotFoundException(msg, ex); 215 } 216 assert (n == nn); 217 218 // allocate a ByteBuffer 219 if (n > Integer.MAX_VALUE) { 220 final String msg = ("size of file " + f 221 + " larger than Integer.MAX_VALUE."); 222 throw new ClassFormatError(msg); 223 } 224 bb = ByteBuffer.allocateDirect((int)n); 225 226 // read file's content into a ByteBuffer 227 try { 228 int k; 229 while ((k = fc.read(bb)) > 0) { 230 out.println(" read " + k + " bytes"); 231 } 232 } catch (IOException ex) { 233 final String msg = ("cannot read file " + f); 234 throw new ClassNotFoundException(msg, ex); 235 } 236 assert (bb.remaining() == 0); 237 bb.rewind(); 238 } finally { 239 try { 240 fc.close(); 241 } catch (IOException ex) { 242 final String msg = ("cannot close FileChannel " + fc); 243 throw new ClassNotFoundException(msg, ex); 244 } 245 try { 246 fis.close(); 247 } catch (IOException ex) { 248 final String msg = ("cannot close FileInputStream " + fis); 249 throw new ClassNotFoundException(msg, ex); 250 } 251 } 252 253 out.println("<-- SelfishFileClassLoader.getClassData(String)"); 254 return bb; 255 } 256 257 // Under the Java ClassLoader delegation model, subclasses are 258 // encouraged to override findClass(), whose default implementation 259 // throws a ClassNotFoundException, rather than loadClass(). 260 // However, for the purpose of ensuring that certain classes are only 261 // loaded by child class loaders, it is not sufficient to override 262 // findClass(), which is invoked by loadClass() only after it has 263 // delegated loading to the parent class loader with no success. loadClass(String name, boolean resolve)264 protected Class<?> loadClass(String name, boolean resolve) 265 throws ClassNotFoundException { 266 out.println("--> SelfishFileClassLoader.loadClass(String, boolean)"); 267 268 // check if the class has already been loaded 269 Class<?> cls = findLoadedClass(name); 270 if (cls != null) { 271 out.println(" already loaded " + cls); 272 return cls; 273 } 274 275 //cls = super.findClass(name); 276 //cls = super.loadClass(name, resolve); 277 278 // load test's classes by this ClassLoader, delegate others 279 if (name.startsWith("test") 280 || name.startsWith("myjapi") 281 || name.startsWith("jtie")) { 282 out.println(" self: loading " + name + " ..."); 283 final ByteBuffer bb = getClassData(name); 284 cls = defineClass(name, bb, null); 285 } else { 286 out.println(" parent: loading " + name + " ..."); 287 cls = getParent().loadClass(name); 288 } 289 assert (cls != null); 290 291 out.println(" ... loaded " + cls 292 + " <" + cls.getClassLoader() + ">"); 293 294 // link class if requested 295 if (resolve) { 296 out.println(" linking " + cls + " ..."); 297 resolveClass(cls); 298 out.println(" ... linked " + cls); 299 } 300 301 out.println("<-- SelfishFileClassLoader.loadClass(String, boolean)"); 302 return cls; 303 } 304 } 305 306 /** 307 * A ClassLoader selectively blocking the delegation of the loading of 308 * classes to parent class loaders. 309 */ 310 static class FilterClassLoader extends ClassLoader { 311 312 // list of package prefixes not to be loaded by parent class loaders 313 protected final String[] prefixes; 314 FilterClassLoader(String[] prefixes)315 public FilterClassLoader(String[] prefixes) { 316 out.println("--> FilterClassLoader()"); 317 out.println(" this = " + this); 318 out.println(" getParent() = " + getParent()); 319 out.println(" prefixes[] = {"); 320 for (int i = 0; i < prefixes.length; i++) { 321 out.println(" \"" + prefixes[i] + "\""); 322 } 323 out.println(" }"); 324 this.prefixes = prefixes; 325 out.println("<-- FilterClassLoader()"); 326 } 327 328 // should never be called, since only invoked by loadClass() findClass(String name)329 protected Class<?> findClass(String name) 330 throws ClassNotFoundException { 331 assert (false) : "should never be called"; 332 throw new ClassNotFoundException(); 333 } 334 335 // selectively load classes by parent class loaders loadClass(String name, boolean resolve)336 protected Class<?> loadClass(String name, boolean resolve) 337 throws ClassNotFoundException { 338 out.println("--> FilterClassLoader.loadClass(String, boolean)"); 339 Class<?> cls = null; 340 341 // this loader does not find and load any classes by itself 342 assert (findLoadedClass(name) == null); 343 344 // give up on loading if class matches any of the prefixes 345 for (int i = prefixes.length - 1; i >= 0; i--) { 346 if (name.startsWith(prefixes[i])) { 347 out.println(" redirect loading " + name); 348 out.println("<<< FilterClassLoader.loadClass(String, boolean)"); 349 throw new ClassNotFoundException(); 350 } 351 } 352 353 // delegate loading of class to parent class loaders 354 out.println(" delegate loading " + name); 355 cls = getParent().loadClass(name); 356 assert (cls != null); 357 //out.println(" ... loaded " + cls 358 // + " <" + cls.getClassLoader() + ">"); 359 360 // link class if requested 361 if (resolve) { 362 out.println(" linking " + cls + " ..."); 363 resolveClass(cls); 364 out.println(" ... linked " + cls); 365 } 366 367 out.println("<-- FilterClassLoader.loadClass(String, boolean)"); 368 return cls; 369 } 370 } 371 372 /** 373 * A URLClassLoader with tracing capabilities for debugging. 374 */ 375 static class MyURLClassLoader extends URLClassLoader { 376 MyURLClassLoader(URL[] urls, ClassLoader parent)377 public MyURLClassLoader(URL[] urls, ClassLoader parent) { 378 super(urls, parent); 379 out.println("--> MyURLClassLoader(URL[], ClassLoader)"); 380 out.println(" this = " + this); 381 out.println(" getParent() = " + getParent()); 382 out.println(" urls[] = {"); 383 for (int i = 0; i < urls.length; i++) { 384 out.println(" " + urls[i]); 385 } 386 out.println(" }"); 387 out.println("<-- MyURLClassLoader(URL[], ClassLoader)"); 388 } 389 findClass(String name)390 protected Class<?> findClass(String name) 391 throws ClassNotFoundException { 392 out.println("--> MyURLClassFinder.findClass(String, boolean)"); 393 out.println(" finding " + name + " ..."); 394 Class<?> cls = super.findClass(name); 395 out.println(" ... found " + cls 396 + " <" + cls.getClassLoader() + ">"); 397 out.println("<-- MyURLClassFinder.findClass(String, boolean)"); 398 return cls; 399 } 400 loadClass(String name, boolean resolve)401 protected Class<?> loadClass(String name, boolean resolve) 402 throws ClassNotFoundException { 403 out.println("--> MyURLClassLoader.loadClass(String, boolean)"); 404 out.println(" loading " + name + " ..."); 405 Class<?> cls = super.loadClass(name, resolve); 406 out.println(" ... loaded " + cls 407 + " <" + cls.getClassLoader() + ">"); 408 out.println("<-- MyURLClassLoader.loadClass(String, boolean)"); 409 return cls; 410 } 411 } 412 413 /** 414 * Attempts to run the JVM's Garbage Collector. 415 */ classPathURLs()416 static private URL[] classPathURLs() throws MalformedURLException { 417 final String cp = System.getProperty("java.class.path"); 418 assert (cp != null) : ("classpath = '" + cp + "'"); 419 final String[] s = cp.split(File.pathSeparator); 420 final URL[] urls = new URL[s.length]; 421 for (int i = 0; i < s.length; i++) { 422 urls[i] = new File(s[i]).toURI().toURL(); 423 } 424 return urls; 425 } 426 test0()427 static public void test0() 428 throws ClassNotFoundException, NoSuchMethodException, 429 IllegalAccessException, InvocationTargetException, 430 MalformedURLException, InstantiationException { 431 out.flush(); 432 out.println("--> MyLoadUnloadTest.test0()"); 433 434 out.println(); 435 out.println(" MyLoadUnloadTest.class.getClassLoader() = " 436 + MyLoadUnloadTest.class.getClassLoader()); 437 out.println(" ClassLoader.getSystemClassLoader() = " 438 + ClassLoader.getSystemClassLoader()); 439 440 // read properties specifying the test to run 441 final String pprefixes = System.getProperty(pprefixes_prop, 442 "test.,myjapi."); 443 final String[] pprefix = pprefixes.split(","); 444 final String cname = System.getProperty(cname_prop, "test.MyJapiTest"); 445 final String mname = System.getProperty(mname_prop, "test"); 446 out.println(); 447 out.println(" settings:"); 448 out.println(" pprefixes = '" + pprefixes + "'"); 449 out.println(" cname = '" + cname + "'"); 450 out.println(" mname = '" + mname + "'"); 451 452 // set up class loaders 453 out.println(); 454 out.println(" create FilterClassLoader ..."); 455 final ClassLoader fcl = new FilterClassLoader(pprefix); 456 out.println(" ... created " + fcl); 457 458 out.println(); 459 out.println(" create URLClassLoader ..."); 460 final URL[] urls = classPathURLs(); 461 //final ClassLoader ucl = new MyURLClassLoader(urls, fcl); 462 final ClassLoader ucl = new URLClassLoader(urls, fcl); 463 out.println(" ... created " + ucl); 464 465 // store class loader in a global but weak reference 466 cl = new WeakReference<ClassLoader>(ucl); 467 468 // run test 469 out.println(); 470 out.println(" load class ..."); 471 Class<?> cls = ucl.loadClass(cname); 472 out.println(" ... loaded " + cls 473 + " <" + cls.getClassLoader() + ">"); 474 475 out.println(); 476 out.println(" get method: " + mname + " ..."); 477 Method m = cls.getMethod("test"); 478 out.println(" ... got method: " + m); 479 480 out.println(); 481 out.println(" create instance: ..."); 482 Object o = cls.newInstance(); 483 out.println(" ... created instance: " + o); 484 485 out.println(); 486 out.println(" invoke method: " + m + " ..."); 487 m.invoke(o); 488 out.println(" ... invoked method: " + m); 489 490 out.println(); 491 out.println("<-- MyLoadUnloadTest.test0()"); 492 out.flush(); 493 } 494 495 /** 496 * Attempts to run the JVM's Garbage Collector. 497 */ gc()498 static private void gc() { 499 out.println("--> MyLoadUnloadTest.gc()"); 500 out.println(" jvm mem: " + (rt.totalMemory() - rt.freeMemory())/1024 501 + " KiB [" + rt.totalMemory()/1024 + " KiB]"); 502 // empirically determined limit after which no further 503 // reduction in memory usage has been observed 504 final int nFullGCs = 100; 505 //final int nFullGCs = 10; 506 for (int i = 0; i < nFullGCs; i++) { 507 long oldfree; 508 long newfree = rt.freeMemory(); 509 do { 510 // help I/O appear in sync between Java/native 511 synchronized (MyLoadUnloadTest.class) { 512 oldfree = newfree; 513 out.println(" rt.gc()"); 514 out.flush(); 515 err.flush(); 516 517 rt.runFinalization(); 518 rt.gc(); 519 newfree = rt.freeMemory(); 520 } 521 } while (newfree > oldfree); 522 } 523 out.println(" jvm mem: " + (rt.totalMemory() - rt.freeMemory())/1024 524 + " KiB [" + rt.totalMemory()/1024 + " KiB]"); 525 out.println("<-- MyLoadUnloadTest.gc()"); 526 } 527 test()528 static public void test() 529 throws ClassNotFoundException, NoSuchMethodException, 530 IllegalAccessException, InvocationTargetException, 531 MalformedURLException, InstantiationException { 532 out.flush(); 533 out.println("--> MyLoadUnloadTest.test()"); 534 535 for (int i = 0; i < 3; i++) { 536 // create a class loader, load native library, and run test 537 test0(); 538 539 // garbage collect class loader that loaded native library 540 gc(); 541 542 // if the class loader with the native library has not been 543 // garbage collected, for instance, due to a strong caching 544 // reference, the next test invocation will fail with, e.g.: 545 // java.lang.UnsatisfiedLinkError: Native Library <libmyjapi> 546 // already loaded in another classloader 547 if (cl.get() != null) { 548 out.println("!!! the class loader with the native library" 549 + " has not been garbage collected (for instance," 550 + " due to a strong caching reference)!"); 551 break; 552 } 553 } 554 555 out.println(); 556 out.println("<-- MyLoadUnloadTest.test()"); 557 out.flush(); 558 } 559 main(String[] args)560 static public void main(String[] args) 561 throws Exception { 562 out.println("--> MyLoadUnloadTest.main()"); 563 564 test(); 565 566 // help I/O appear in sync between Java/native 567 synchronized (MyLoadUnloadTest.class) { 568 //try { 569 // MyLoadUnloadTest.class.wait(1000); 570 //} catch (InterruptedException ex) { 571 // // ignore 572 //} 573 out.flush(); 574 err.flush(); 575 } 576 577 out.println(); 578 out.println("<-- MyLoadUnloadTest.main()"); 579 } 580 } 581