CORBA supports both remote (across a network) and co-located invocation of object references. Obviously the remote case requires mechanisms such as connections and marshalling in order to communicate remotely. However, we do not want to pay this high overhead for communicating with an object reference when it is co-located with the invoker. For this reason, the RMI-IIOP standard supports a co-located call optimization at the stub level.
An RMI-IIOP stub consists of two parts:
This co-located call optimization obtains the servant (i.e. the implementation of the remote interface) from the delegate by the servant_preinvoke method, and then directly invokes the method on the servant, completely bypassing all transport mechanisms in the ORB. However, the CORBA and J2EE specifications require that co-located calls preserve the same semantics as remote ones, in that all objects must be copied when they are passed on a co-located call. The reason for this is to avoid interference between client and server threads in the co-located case that could not occur in the remote case.
This object copying is commonly performed by using a CDR stream to copy the objects. This bypasses the transport overhead associated with TCP/IP connections, but the cost of copying the data is still considerable. Any kind of marshalling stream must do certain operations, such as linearizing the data, that are not necessary when an object is simply copied.
I have a benchmark on object copying that runs as part of the CORBA units tests. The test copies data created by NonFinalComplexClass.makeNonFinalComplexGraph() in a loop 2000 times. Here is the class that is copied:
private static class NonFinalComplexClass implements java.io.Serializable { public boolean publicBoolean = false ; protected char protectedChar = 'D' ; private byte privateByte = (byte)3 ; short shrt = (short)-2345 ; public int finalPublicInt = 273415 ; protected long finalProtectedLong = 38958284 ; private float finalPrivateFloat = (float)3.1415926535 ; double finalDouble = 2.718281828 ; String str1 ; String str2 ; private Object finalPrivateObject1 = new HashMap() ; private Object finalPrivateObject2 = finalPrivateObject1 ; public Object[] references ; public NonFinalComplexClass( String name ) { str1 = name ; str2 = str1 ; } public static NonFinalComplexClass makeNonFinalComplexClass(String str ) { return new NonFinalComplexClass( str ) ; } public static NonFinalComplexClass makeNonFinalComplexClassAliasedArray(String str ) { int num = 5 ; NonFinalComplexClass[] classes = new NonFinalComplexClass[ num ] ; for (int ctr = 0; ctr<num; ctr++ ) { classes[ctr] = makeNonFinalComplexClass( str + ":member " + ctr ) ; if (ctr==0) { // 0th classes references all others classes[ctr].references = new NonFinalComplexClass[num] ; } else { // others reference only 0th, but allocate // different sizes reference arrays classes[ctr].references = new NonFinalComplexClass[ctr] ; classes[ctr].references[0] = classes[0] ; } // Make 0th class reference the others classes[0].references[ctr] = classes[ctr] ; } return classes[0] ; } public static NonFinalComplexClass makeNonFinalComplexClassGraph() { int num = 5 ; NonFinalComplexClass[] classes = new NonFinalComplexClass[ num ] ; for (int ctr = 0; ctr<num; ctr++ ) { classes[ctr] = makeNonFinalComplexClassAliasedArray( "group " + ctr ) ; if (ctr==0) { // 0th classes references all others classes[ctr].references = new NonFinalComplexClass[num] ; } else { // others reference only 0th, but allocate // different sizes reference arrays classes[ctr].references = new NonFinalComplexClass[ctr] ; classes[ctr].references[0] = classes[0] ; } // Make 0th class reference the others classes[0].references[ctr] = classes[ctr] ; } return classes[0] ; } }
My testbed is configured as follows:
This results in the following copy times for the different copiers that we currently support:
Copier Implementation | Object time | Array time | AliasedArray time | Graph time |
newreflect | 121 | 342 | 950 | 923 |
newreflect fallback | 97 | 363 | 874 | 862 |
old reflect | 92 | 273 | 1153 | 1115 |
ORB Stream | 72 | 1568 | 4267 | 3671 |
Java Stgream | 51 | 660 | 1156 | 1189 |
All times are given in microseconds needs to copy the object. There are a number of intersting points about the data in this table:
The ORB currently supports both the optimized reflective copier, and an object copier that simply passes all objects through by reference without doing any copies. Either copier can be selected for each EJB deployed in the app server by means of the CopyObjectPolicy (in spi.extension), which can be applied to any POA. Since each EJB has its own POA, we can configurate copying behavior independently for each EJB.
Like most things in the ORB, object copying is configured by registering a factory against the ORB. The default configuration of the ORB contains only the standard stream-based copier, but the AS 8.1 initialization includes two others: the newreflect fallback copier mentioned in the benchmark, and also the pass-by-reference "copier", which actually does no copies at all. The configuration in the app server is handled in appserv-core in the com.sun.enterprise.iiop.PEORBConfigurator.configureCopiers method.
The basic interface for copyObject is simply:
package com.sun.corba.se.spi.copyobject ; /** Provides an interface for a variety of means to copy an arbitrary object. * Any implementation of this interface must return an exact copy of obj, * preserving all aliasing across all objects reachable from obj. * ReflectiveCopyException must be thrown if the implementation cannot copy * obj for some reason. Note thyat a trivial implementation of this * interface is possible (always return obj), but this is often not the * desired implementation. */ public interface ObjectCopier { Object copy( Object obj ) throws ReflectiveCopyException ; }
This method will copy any object presented to it. Multiple calls to copy on the same instance of an ObjectCopier implementation will preserve all aliasing between different objects reachable from objects passed as arguments to copy.
In addition, there is a simple factory interface:
package com.sun.corba.se.spi.copyobject ; public interface ObjectCopierFactory { ObjectCopier make() ; }
This trivial API is provided so that different ObjectCopier implementations can be registered with the ORB's CopierManager, which has the following interface:
package com.sun.corba.se.spi.copyobject ; /** Manager of ObjectCopier implementations used to support javax.rmi.CORBA.Util.copyObject(s). * This provides simple methods for registering all supported ObjectCopier factories. * A default copier is also supported, for use in contexts where no specific copier id * is available. */ public interface CopierManager { /** Set the Id of the copier to use if no other copier has been set. */ void setDefaultId( int id ) ; /** Return the copier for the default copier id. Throws a BAD_PARAM exception * if no default copier id has been set. */ int getDefaultId() ; ObjectCopierFactory getObjectCopierFactory( int id ) ; ObjectCopierFactory getDefaultObjectCopierFactory() ; /** Register an ObjectCopierFactory under a particular id. This can be retrieved * later by getObjectCopierFactory. */ void registerObjectCopierFactory( ObjectCopierFactory factory, int id ) ; }
(explain this)
For direct use, an application could directly obtain the needed ObjectCopierFactory from one of the copy object defaults classes: