Tom Studer's Home Page
[ start | index | login ]
start > Sharing Java objects between class loader instances

Sharing Java objects between class loader instances

Created by tom. Last edited by tom, 5 years and 27 days ago. Viewed 3,468 times. #6
[diff] [history] [edit] [rdf]
labels
attachments
registry14.gif (8532)
registry15.gif (8437)

The constraints

A native library can only be loaded once per VM. This is generally not a problem. The native library and its Java wrapper class can be looked at as a Singleton which gets instantiated only once (e.g. when it is first used). The loading is controlled via a static initializer in the class or some static flag telling whether the library has already been loaded. This works as long as your application's code accessing the library is associated with the same class loader instance. With more than one class loader, our native library Singleton would be instantiated for each class loader instance, resulting in the native library to be bound into the VM more than once, resulting in an exception being thrown (something like "Native library already loaded by another class loader"). Check out >>http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4225434 for a discussion on this.

Applets running within the same plug-in VM share their applet class loader if and only if their code base is the same and the value of the "archive" attribute of the applet or object tag is the same. If you're loading native libraries in an applet, make sure this is the case and everything works fine. However, sometimes this is difficult to achieve. For example, I worked in a project where we ran an applet to check the local PC's registry using a native library. The applet used different sets of configuration data on each run. Because of company security policy, the applet and the configuration data had to be signed. In order to use the plug-in/browser infrastructure for Jar file verification, we included the configuration data in a Jar file which we put on the applet's archive "path". Not including the config Jar as part of the "archive" tag would have meant downloading it by the applet and "manually" verifying its signature by custom code in the applet - something we didn't want to do.

As a result of the changing archive attribute, however, we had the situation of different applet class loaders being used by the plug-in when running our applet. Because of this, our applet couldn't reliably access the native library anymore (the initial run of the applet in a browser session would work, subsequent runs of the applet would produce the mentioned exception).

The solution we found involves loading the native library only once and sharing the Java wrapper object for the library between class loader instances. The following sections describe how this can be done:

Sharing Objects between Class-Loader instances within the same plug-in VM

The solution we found is quite hairy and involves "hi-jacking" an object referenced by System.out. System.out in the plug-in writes to the common plug-in console. There is only one System.out/console per plug-in VM. Classes loaded via different applet class loaders still write to the same System.out.

The following two class diagrams depict the solution. The first one for JDK 1.4 and the second one for JDK 1.5:

registry14

Diagram 1 - Sharing an instance of class Registry via System.out in JDK 1.4

In diagram 1, System.out is an instance of PrintStream. PrintStream extends FilterOutputStream which has a property "out" pointing to a DebugOutputStream. DebugOutputStream in turn contains a reference to a (the) MainConsoleWriter which in turn internally uses an object consoleSynchObject for synchronization purposes. In this solution we store our shared object (instance of Registry) by storing it in the consoleSynchObject field, replacing the plain Object instance already there.

registry15

Diagram 2 - Sharing an instance of class Registry via System.out in JDK 1.5

Just like in the previous case, we have System.out being an instance of FilterOutputStream. However, FilterOutputStream's "out" property is of type TracePrintStream. And there are no suitable member variables which can be used to store our object. The solution is to replace the TracePrintSream object stored in "out" with an instance of the new class DaiPrintStream ("Dai" stands for "desktop application integration" - the feature our applet implements). DaiPrintStream extends TracePrintStream, making it type compatible to TracePrintStream. At the same time, it delegates to the original TracePrintStream object, making sure that any System.out output ends up in the original TracePrintStream object. Our shared "Registry" object is now simply stored in the registry property of DaiPrintStream.

In order to store our Registry object using one of the approaches described above, object properties must be accessed which are not publicly accessible to our applet. The work-around here is to use reflection to navigate the object hierarchy and to store the Registry object. Because of this, our applet needs the following security permissions:

permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.reflect.ReflectPermission "suppressAccessChecks";

Of course, it needs other permissions as well; for example it needs the permission to load the native library associated with the Registry object (the one we're doing this whole who-ha for).

Putting it together

The following points describe the steps performed by our applet to call a native method via our Registry class when run under plug-in JDK 1.4:

  1. The applet navigates the object structure dangling off System.out until it gets to the consoleSynchObject property of MainConsoleWriter (Diagram 1). This is done using reflection since the relevant objects and properties are not publicly accessible.
  2. Having obtained a hold on consoleSynchObject, it checks if it is of type Registry (via class name, not via instanceof operator - instanceof will return false when comparing instances of the same class across different class loaders).
  3. If it is of type Registry, we know that the registry object with its native library have already been loaded during a previous applet invocation. The applet now simply uses this object.
  4. If it is not of type Registry (i.e. it is the original Object instance), the applet creates a new Registry object (loading the associated native library) and stores it in the consoleSynchObject member.
  5. At this point, the applet holds a reference to the Registry object, either freshly instantiated or instantiated by a different applet at an earlier time.
  6. The applet calls the methods of the Registry object to do its native thing.
There is one more complication: The object reference to our Registry object, if instantiated by a different applet from a different class loader, can not be cast to type Registry even though it is an object instance of that class. This is because the cast, like the instanceof operator, only works within the same applet class loader. This could be dealt with by serializing/deserializing the object. In our case, however, we're using reflection to call the methods of the Registry object we're interested in. (Note: if anybody knows of a simple way to cast an Object across class loader boundaries, please let me know: tstuder<at>conjective.ch)

no comments | post comment
mouse_cartoon



< May 2012 >
SunMonTueWedThuFriSat
12345
6789101112
13141516171819
20212223242526
2728293031

snipsnap-help | More Help

Powered by SnipSnap 1.0b2-uttoxeter

Describe here what your SnipSnap is about!

Configure this box!

  1. Login in
  2. Click here: snipsnap-portlet-2
  3. Edit this box
snipsnap.org | Copyright 2000-2002 Matthias L. Jugel and Stephan J. Schmidt