Add comment on SnappyLoader.load

This commit is contained in:
Taro L. Saito 2011-08-01 15:19:28 +09:00
parent 7931742179
commit 199b12e61c
3 changed files with 61 additions and 39 deletions

View File

@ -36,7 +36,6 @@ import java.net.URL;
import java.security.DigestInputStream; import java.security.DigestInputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.ProtectionDomain;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
@ -91,6 +90,7 @@ public class SnappyLoader
cl = cl.getParent(); cl = cl.getParent();
} }
return cl; return cl;
//return ClassLoader.getSystemClassLoader();
} }
private static byte[] getByteCode(String resourcePath) throws IOException { private static byte[] getByteCode(String resourcePath) throws IOException {
@ -112,6 +112,28 @@ public class SnappyLoader
return isLoaded; return isLoaded;
} }
/**
* Load SnappyNative and its JNI native implementation in the root class
* loader. This is necessary to avoid JNI multi-loading issues when the same
* JNI library is loaded by different class loaders in the same JVM.
*
* In order to load native code in the root class loader, this method first
* inject SnappyNativeLoader class into the root class loader, because
* {@link System#load(String)} method uses the class loader of the caller
* class when loading native libraries.
*
* <pre>
* (root class loader) -> [SnappyNativeLoader, SnappyNative, SnappyNativeAPI, SnappyErrorCode] (injected by this method)
* |
* |
* (child class loader) -> See the above classes loaded by the root class loader.
* Then creates SnappyNativeAPI implementation by instantiating SnappyNaitive class.
* </pre>
*
*
*
* @return
*/
static synchronized SnappyNativeAPI load() { static synchronized SnappyNativeAPI load() {
if (isInitialized) if (isInitialized)
@ -119,9 +141,9 @@ public class SnappyLoader
isInitialized = true; isInitialized = true;
final String nativeLoaderClassName = "org.xerial.snappy.SnappyNativeLoader"; final String nativeLoaderClassName = "org.xerial.snappy.SnappyNativeLoader";
final String[] classesToPreload = new String[] { "org.xerial.snappy.SnappyNativeAPI",
"org.xerial.snappy.SnappyNative", "org.xerial.snappy.SnappyErrorCode" };
// Use parent class loader to load SnappyNative, since Tomcat, which uses different class loaders for each webapps, cannot load JNI interface twice
ClassLoader rootClassLoader = getRootClassLoader();
try { try {
Class< ? > c = Class.forName(nativeLoaderClassName); Class< ? > c = Class.forName(nativeLoaderClassName);
// If this native loader class is already defined, it means that another class loader already loaded the native library of snappy // If this native loader class is already defined, it means that another class loader already loaded the native library of snappy
@ -131,9 +153,12 @@ public class SnappyLoader
} }
catch (ClassNotFoundException e) { catch (ClassNotFoundException e) {
try { try {
// Load a byte code // Load a byte code
byte[] byteCode = getByteCode("/org/xerial/snappy/SnappyNativeLoader.bytecode"); byte[] byteCode = getByteCode("/org/xerial/snappy/SnappyNativeLoader.bytecode");
// In addition, we need to load the other dependent classes (e.g., SnappyNative and SnappyException) using the system class loader // In addition, we need to load the other dependent classes (e.g., SnappyNative and SnappyException) using the system class loader
final String[] classesToPreload = new String[] { "org.xerial.snappy.SnappyNativeAPI",
"org.xerial.snappy.SnappyNative", "org.xerial.snappy.SnappyErrorCode" };
List<byte[]> preloadClassByteCode = new ArrayList<byte[]>(classesToPreload.length); List<byte[]> preloadClassByteCode = new ArrayList<byte[]>(classesToPreload.length);
for (String each : classesToPreload) { for (String each : classesToPreload) {
preloadClassByteCode.add(getByteCode(String.format("/%s.class", each.replaceAll("\\.", "/")))); preloadClassByteCode.add(getByteCode(String.format("/%s.class", each.replaceAll("\\.", "/"))));
@ -142,21 +167,19 @@ public class SnappyLoader
// Create SnappyNative class from a byte code // Create SnappyNative class from a byte code
Class< ? > classLoader = Class.forName("java.lang.ClassLoader"); Class< ? > classLoader = Class.forName("java.lang.ClassLoader");
Method defineClass = classLoader.getDeclaredMethod("defineClass", new Class[] { String.class, Method defineClass = classLoader.getDeclaredMethod("defineClass", new Class[] { String.class,
byte[].class, int.class, int.class, ProtectionDomain.class }); byte[].class, int.class, int.class });
// Use parent class loader to load SnappyNative, since Tomcat, which uses different class loaders for each webapps, cannot load JNI interface twice //ProtectionDomain pd = System.class.getProtectionDomain();
ClassLoader systemClassLoader = getRootClassLoader();
ProtectionDomain pd = System.class.getProtectionDomain();
// ClassLoader.defineClass is a protected method, so we have to make it accessible // ClassLoader.defineClass is a protected method, so we have to make it accessible
defineClass.setAccessible(true); defineClass.setAccessible(true);
try { try {
// Create a new class using a ClassLoader#defineClass // Create a new class using a ClassLoader#defineClass
defineClass.invoke(systemClassLoader, nativeLoaderClassName, byteCode, 0, byteCode.length, pd); defineClass.invoke(rootClassLoader, nativeLoaderClassName, byteCode, 0, byteCode.length);
for (int i = 0; i < classesToPreload.length; ++i) { for (int i = 0; i < classesToPreload.length; ++i) {
byte[] b = preloadClassByteCode.get(i); byte[] b = preloadClassByteCode.get(i);
defineClass.invoke(systemClassLoader, classesToPreload[i], b, 0, b.length, pd); defineClass.invoke(rootClassLoader, classesToPreload[i], b, 0, b.length);
} }
} }
finally { finally {
@ -164,14 +187,10 @@ public class SnappyLoader
defineClass.setAccessible(false); defineClass.setAccessible(false);
} }
// Load the snappy loader class // Load the SnappyNativeLoader class
Class< ? > loaderClass = systemClassLoader.loadClass(nativeLoaderClassName); Class< ? > loaderClass = rootClassLoader.loadClass(nativeLoaderClassName);
if (loaderClass != null) { if (loaderClass != null) {
final String KEY_SNAPPY_NATIVE_LOAD_FLAG = "org.xerial.snappy.native_is_loaded";
boolean isNativeLibLoaded = Boolean.parseBoolean(System.getProperty(KEY_SNAPPY_NATIVE_LOAD_FLAG,
"false"));
if (!isNativeLibLoaded) {
File nativeLib = findNativeLibrary(); File nativeLib = findNativeLibrary();
if (nativeLib != null) { if (nativeLib != null) {
// Load extracted or specified snappyjava native library. // Load extracted or specified snappyjava native library.
@ -180,20 +199,15 @@ public class SnappyLoader
} }
else { else {
// Load preinstalled snappyjava (in the path -Djava.library.path) // Load preinstalled snappyjava (in the path -Djava.library.path)
Method loadMethod = loaderClass.getDeclaredMethod("loadLibrary", Method loadMethod = loaderClass.getDeclaredMethod("loadLibrary", new Class[] { String.class });
new Class[] { String.class });
loadMethod.invoke(null, "snappyjava"); loadMethod.invoke(null, "snappyjava");
} }
System.setProperty(KEY_SNAPPY_NATIVE_LOAD_FLAG, "true");
}
// And also, preload the other dependent classes // And also, preload the other dependent classes
for (String each : classesToPreload) { for (String each : classesToPreload) {
systemClassLoader.loadClass(each); rootClassLoader.loadClass(each);
} }
isLoaded = true; isLoaded = true;
api = (SnappyNativeAPI) Class.forName("org.xerial.snappy.SnappyNative").newInstance(); api = (SnappyNativeAPI) Class.forName("org.xerial.snappy.SnappyNative").newInstance();
return api; return api;
} }

View File

@ -32,6 +32,8 @@ import java.nio.ByteBuffer;
* method in this class is defined in SnappyNative.h (genereted by javah) and * method in this class is defined in SnappyNative.h (genereted by javah) and
* SnappyNative.cpp * SnappyNative.cpp
* *
* NEVER USE THIS CLASS DIRECTROY since it breaks the native library code
* loading.
* *
* @author leo * @author leo
* *

View File

@ -96,6 +96,7 @@ public class SnappyLoaderTest
ClassRealm L2 = cw.newRealm("l2", URLClassLoader.newInstance(new URL[] { classPath }, parent)); ClassRealm L2 = cw.newRealm("l2", URLClassLoader.newInstance(new URL[] { classPath }, parent));
// Actually load Snappy.class in a class loader // Actually load Snappy.class in a class loader
try {
Class< ? > S1 = L1.loadClass("org.xerial.snappy.Snappy"); Class< ? > S1 = L1.loadClass("org.xerial.snappy.Snappy");
Method m = S1.getMethod("getNativeLibraryVersion"); Method m = S1.getMethod("getNativeLibraryVersion");
String v = (String) m.invoke(null); String v = (String) m.invoke(null);
@ -106,6 +107,11 @@ public class SnappyLoaderTest
String v2 = (String) m2.invoke(null); String v2 = (String) m2.invoke(null);
assertEquals(v, v2); assertEquals(v, v2);
}
catch (Exception e) {
e.printStackTrace();
throw e;
}
} }