ObjectStore Java API User Guide
Chapter 12

Miscellaneous Information

This chapter provides miscellaneous information about ObjectStore.

This chapter discusses the following topics:

Java-Supplied Persistence-Capable Classes

Description of Special Behavior of String Literals

Serializing Persistent Objects

Using Persistence-Capable Classes in a Transient Manner

Description of Java Persistent Storage Layouts

Differences Between C++ and Java Interfaces to ObjectStore

Environment Variables

Java-Supplied Persistence-Capable Classes

Some Java-supplied classes are persistence-capable. Others are not persistence-capable and cannot be made persistence-capable. A third category of classes can be made persistence-capable, but there are important issues to consider when you do so.

Description of Java-Supplied Persistence-Capable Classes

The following Java classes are persistence-capable:

Identity

ObjectStore does not always preserve identity for objects that are instances of the Java wrapper classes. It is more efficient to store these objects as values rather than as objects. Because identity is not always preserved, programs that use object identity to compare wrapper class objects work differently when used with persistent objects. For example, this method is incorrect:

boolean comparePersistIntegers(Integer x, Integer y) {
      return (x == y);
}
Instead, it should be written as

boolean comparePersistIntegers(Integer x, Integer y) {
      return x.equals(y);
}
Additional information about object identity is in About Object Identity.

VM overhead

When ObjectStore makes them persistent, String types, primitive wrapper types, and arrays have more runtime virtual memory overhead than types that implement IPersistent. This is because ObjectStore must create entries for these types in two hash tables. COM.odi.IPersistent requires an entry in a single hash table since some information is stored in fields in the object.

Persistent and persistence-capable

In your program, some wrapper objects might be persistent and some might be transient, though persistence-capable.

Any routine that requires a persistent object, as opposed to a persistence-capable object, notices the distinction between persistent and persistence-capable but transient. For example, if an application calls Segment.of() on an Integer object, the return value might be a segment in a database or ObjectStore might throw ObjectNotPersistentException. You cannot always predict what the return value will be because an Integer-valued field in a persistent object can contain either a persistent or transient value.

Unicode strings

ObjectStore stores Unicode strings. You can specify any Java string with Unicode characters in it, and ObjectStore can store it persistently and retrieve it correctly. ObjectStore uses UTF-8 encoding/compression to store regular English strings compactly. Sun's Java implementation uses the same mechanism.

Can Other Java-Supplied Classes Be Persistence-Capable?

There are many Java system classes that cannot be persistence-capable. There are other Java system classes that you can make persistence-capable, but you must consider some issues when you do so. In some situations, you can subclass the Java system class and make the subclass persistence-capable. Of course, this would not work for final classes.

Primitive types

You cannot store an object of a primitive type, such as an int, directly in a database as a discrete object. To store an object of a primitive type in a database you can

For example, you cannot make byte persistence-capable because all by itself, a byte is not an Object. But you can make byte[] persistence-capable because it is an Object.

Native methods

Classes that use native methods cannot be made persistence-capable by the postprocessor because the postprocessor cannot annotate the native methods the way it can annotate Java code. What this means is that if a class has native methods, and you postprocess the class, ObjectStore cannot guarantee that everything will work properly.

You might choose to postprocess your code and then add native code. If you do this, you must ensure that any persistent objects that your native code references are properly fetched from the database before the native method is called. Be careful, however, if a native method changes the value of an indexed instance variable. This does not work properly because the index is not updated.

Classes that hold state

Other system classes do not make sense as persistent objects because they hold state that is inherently tied to the process, such as open file channels or Java threads.

Postprocessing

For other classes, like java.lang.Stringbuffer, the above obstacles might not apply. If you postprocess the .class file for java.lang.StringBuffer and specify the -modifyjava option, the postprocessor produces a persistence-capable StringBuffer class:

osjcfp -dest \osjcfpout -modifyjava java.lang.StringBuffer
Then you must put the new .class file in your CLASSPATH variable ahead of the standard Java .class file. All subsequent use of the StringBuffer class in this environment would use the persistence-capable version.

Performance drawbacks

There are, however, some drawbacks to doing this. There will be some slowdown of some or all the methods, because the postprocessor must add new instructions to check whether the object needs to be brought in from the database or needs to be marked as modified.

How much slowdown is hard to determine. It depends on the details of the method. Even parts of your program that never handle persistence are affected by these extra instructions. This also applies to indirect uses of the class, for example, if StringBuffer is used heavily in some Java library that you are using, such as a user interface or network library.

Library version problems

There can also be problems with Java library version skew. If you postprocess java.lang.StringBuffer from version 1.1 of the Java Virtual Machine, and then your user uses your program with version 1.1.2, and StringBuffer has changed in some way between 1.1 and 1.1.2, your user will see the 1.1 version (persistence-capable) everywhere in the entire Java environment. If your user was depending, directly or indirectly, on the new 1.1.2 version of StringBuffer, something might not work properly.

Renaming the class

You might need to rename the newly created persistence-capable version so that the non-persistence-capable version is still available to the other Java system classes. To do this, specify the -translatepackage option when you run the postprocessor.

This avoids the problem and is generally safer. However, you might need the persistence-capable class to have the original class name. For example, suppose you have a library that has a method that takes an argument of type java.lang.Stringbuffer. You want to pass in a persistence-capable object. You cannot rename the class because the argument type would not match.

java.util.Hashtable

ObjectStore itself uses java.util.Hashtable. Consequently, invoking Java or using ObjectStore with a persistence-capable version of java.util.Hashtable that is available in your CLASSPATH is likely to cause trouble, such as infinite loops. A better approach is to substitute the ObjectStore-supplied class COM.odi.util.OSHashtable.

Description of Special Behavior of String Literals

There are special considerations when making String literals persistent.

When a Java program refers to a String literal by using quotation marks to name a string, Java treats the resulting String as a constant value. Multiple calls to a method with the String literal operate on the same String object.

The COM.odi.stringPoolSize initialization property allows you to control the way that ObjectStore causes Strings, other than literals with the same contents, to be represented by a single, shared instance in the database in certain circumstances.

Example of String Behavior

Consider the following example:

Object string() { String result = "string"; return result; }
Object intArray() { int[] result = { 1, 2, 3 }; return result; }
boolean stringsTheSame() { return string() == string(); }
boolean intArraysTheSame() { return intArray() == intArray(); }
The stringsTheSame() method always returns true because every call to string() returns the exact same String object. The intArraysTheSame() method always returns false because each call to intArray() constructs a new int[] object.

The behavior of String literals in Java has implications for making strings persistent. If a String literal becomes persistent, then subsequent calls to the method that contains the literal find that the string is already persistent. This can cause trouble if different calls to the method attempt to store references to the string in objects stored in different segments.

The following example demonstrates this problem:

void makeSomeObjects(Segment segment1, Segment segment2) {
      ObjectStore.migrate(makeObject(), segment1, false);
      ObjectStore.migrate(makeObject(), segment2, false);
}
Object makeObject() {
      Object[] result = new Object[1];
      result[0] = "string";
      return result;
}
The first call to makeObject() causes the "string" literal to be migrated into segment1. The second call to makeObject() tries to store a reference to "string", which is now an unexported object in segment1, in an object in segment2.

The simplest solution to the problem is to avoid storing String literals in objects that become persistent.

The following example shows a modification to the makeObject() method that fixes this problem:

Object makeObject() {
      Object[] result = new Object[1];
      result[0] = new String("string");
      return result;
}
In this version, the makeObject() method stores a new String instance in each object that it returns. The result is that a new String instance is stored in the database for each call to makeObject().

Another solution is to create an exported String object and have makeObject() always use that object. Decide on the approach you want to use according to the way you want to cluster objects in the database.

Another situation to consider is when an updated object refers to a String that has the same identity it had when the object was read from the database. ObjectStore does not store a new String in the database. If the updated object refers to a String with a different identity and that String is not stored in the database, ObjectStore migrates the String into the database. This is regardless of whether or not the database contains another String with the same contents.

As in Java, strings in ObjectStore are immutable. To change a string, you can destroy the old one and create a new one.

Destroying Strings

By default, String objects that become persistent during a transaction revert to being transient at the end of the transaction. Persistent objects are usually made stale at the end of a transaction. Unlike objects that implement IPersistent, when a String is made stale it becomes transient. As a result, the problem with making String literals persistent only occurs if the String literal is seen several times in the same transaction. If the String literal is only incorporated in a persistent data structure once in a transaction, then the problem does not occur.

When you destroy a String, in the transaction in which the destroy operation occurs, ObjectStore keeps track of the fact that the object was destroyed. An attempt to use a destroyed String literal causes ObjectStore to throw ObjectNotFoundException. The solution is to copy the String before you destroy it.

You should not destroy a String in a database unless you know that no other object in the database refers to that String. A safe, though possibly inefficient, way to handle this is to use

new String(String) 
to force a new identity to each String that might be referenced. Also, you must disable the String pool by specifying 0 for the value of the COM.odi.stringPoolSize initialization property. This allows you to be sure that you can safely destroy the old String instance.

It is usually best to avoid destroying strings (or objects) altogether and let the persistent garbage collector take care of destroying such unreachable objects. The persistent garbage collector can typically destroy and reclaim such objects very efficiently, since it can batch such operations and cluster them effectively. If you set up the GC to run when the system is lightly loaded, you can effectively defer the overhead of the destroy operations to a time when your system would otherwise be idle, thus getting greater real throughput from your application when you really need it.

Serializing Persistent Objects

You can serialize many classes that implement COM.odi.IPersistent. For this to work, the definition of your persistence-capable class must implement the java.io.Serializable interface. The classes you can serialize include COM.odi.util.OSVector and COM.odi.util.OSHashtable.

During serialization, none of the transient fields in the IPersistent implementation need to be written out.

Before serializing an object, an application must always invoke ObjectStore.deepFetch() on the object to be serialized. The deepFetch() method ensures that the contents of all components of the object are accessible. This must be the case for an application to serialize an object.

Background about the necessity for deepFetch()

In an ObjectStore application, the first time you read or modify an object, ObjectStore makes the contents of the object available. The contents do not have to be available before you start the operation. You do not have to add Java code to make the contents available. When an ObjectStore program follows a reference from a source object to a target object, it automatically makes the contents of the target object available. This happens because the postprocessor recognizes the Java byte-code instructions that follow references and it inserts the code that fetches the object contents.

Serialization works differently. It follows references from one Java object to another without using Java byte codes. Serialization does not perform the automatic fetches the way that ObjectStore does. Consequently, before you initiate serialization of an object, its contents and the contents of all its components must already be available. The ObjectStore.deepFetch() operation does this for you.

Limitation

You cannot serialize Java peer objects. Consequently, you cannot serialize ObjectStore collection objects.

Example

When an application serializes and deserializes a persistent object with the default serialization methods, ObjectStore effectively creates a transient copy of the object and its components. Here is code that provides an example of serializing and deserializing persistent objects. In this example, list2 is a transient copy of the persistent list.

public
class SerializationExample {
public static void main(String argv[])
      throws java.lang.ClassNotFoundException, java.io.IOException,
      java.io.FileNotFoundException {
      String dbName = argv[0];
      Session.createGlobal(null, null);
      /* Create a database with a list in it. */
      Database db = Database.create(dbname, 
            ObjectStore.ALL_READ | ObjectStore.ALL_WRITE);
      Transaction tr = Transaction.begin(ObjectStore.UPDATE);
      List curr = new List("1", null);
      db.createRoot("list", curr);
      for (int i=2; i < 5; i++) {
            curr.next = new List(""+i, null);
            curr = curr.next;
       }
      tr.commit();
      /* Illustrate use of serialization in this example. */
      tr = Transaction.begin(ObjectStore.UPDATE);
      List head = (List)db.getRoot("list");
      /* Fetch the entire list prior to serializing it. */
      ObjectStore.deepFetch(head);
      FileOutputStream f = new FileOutputStream("tmp");
      ObjectOutputStream os = new ObjectOutputStream(f);
      os.writeObject(head);
      FileInputStream in = new FileInputStream("tmp");
      ObjectInputStream is = new ObjectInputStream(in);
      /* list2 is effectively a copy of the list denoted by head. */
      List list2 = (List)is.readObject();
      ...
      tr.commit();
      }
}
public class List implements java.io.Serializable {
      public Object value;
      public List next;
      List(Object value, List next) {
            this.value = value;
            this.next = next;
      }
      ...
}

Using Persistence-Capable Classes in a Transient Manner

The stublib.zip file contains stubs of ObjectStore classes that allow user-defined persistence-capable classes to be used in a purely transient manner. The annotations in the persistence-capable classes make calls to the various ObjectStore stub routines in stublib.zip.

The stublib.zip file provides a stripped down version of the ObjectStore API. This allows better performance and a smaller footprint than the complete zip file.

For example, you might want to use stublib.zip for the client in an RMI or CORBA application. The client might use persistence-capable classes, which make references to various ObjectStore methods, but the client never directly accesses a ObjectStore database. In this situation, the stub routines in stublib.zip satisfy the requirements of the Java VM's linker.

To use stublib.zip, put it in your CLASSPATH instead of osji.zip.

If your application uses any classes in COM.odi.util, you must use osji.zip. You cannot use stublib.zip because the stub definitions are not sufficient for the COM.odi.util classes.

Description of Java Persistent Storage Layouts

There are some differences between databases created by the Java and C++ interfaces to ObjectStore. These differences result in some restrictions on the use of databases by both the C++ and Java interfaces.

Databases created by the Java interface can be used by C++ programs, but the representation of Java primary objects is different from regular C++ objects. Because of these differences, accessing the contents of Java primary objects from C++ programs is not currently supported. C++ programs can store, access, and modify C++ objects in databases created by or modified by the Java interface.

Databases created by the C++ interface can be read or modified by the Java interface. C++ objects can be manipulated as described in the book Developing ObjectStore Java Applications That Access C++. In addition, the Java interface can store Java primary objects in databases created by the C++ interface.

Databases that hold Java primary objects have a segment that contains information used by the Java interface to describe the schema for the classes of the Java objects stored in the database. This segment also contains information about the location of other information used by the Java interface, which is stored in each segment that contains Java primary objects.

Segments that contain Java primary objects contain C++ data structures that describe the exported objects stored in the segment.

ObjectStore does not always align objects by page boundaries. Any Java object might cross page boundaries.

All Java primary objects contain a four-byte object header followed by data for the object fields. Java arrays are represented by a 12-byte header object and a separate C++ array that contains the array contents.

Fields of Java primary objects that contain object references are represented by an eight-byte or twelve-byte data structure rather than the four-byte pointer usually used by C++ objects. The larger data structure allows the Java interface to provide more features for object references. The eight-byte data structure is used for Java object reference fields that cannot contain arrays. This includes fields that are not of type Object or of an array type. The twelve-byte data structure is used for Object fields and fields of type array. The following table provides the exact element sizes.

Element Type Element Size
byte 1
short 2
char 2
int 4
float 4
double 8
long 8
Object reference 8 or 12

Object references that are of type java.lang.Object, java.land.Double, java.lang.Long, or any array type, require 12 bytes. All other object references require eight bytes.

Differences Between C++ and Java Interfaces to ObjectStore

Here are some differences betweeen the Java and C++ interfaces to ObjectStore.

Timing of the Write Lock Acquisition

In the C++ interface to ObjectStore, as soon as you modify an object, you set (or try to set) the ObjectStore write lock for the page that the object is on. But in the Java interface to ObjectStore, this might or might not happen depending on the lazy write locking flag. The default is that it does not happen, and the write locking is deferred until later. So, if you have two sessions (in two VMs) that are accessing the same data and they are not both just reading, different timing of the write lock acquisition can cause the behavior to be different in the Java interface than it is in the C++ interface.

Opening the Same Database Multiple Times

In the Java interface to ObjectStore, each subsequent opening of a database after the initial open operation returns the same database object. For example:

db1 = Database.open("foo", ObjectStore.UPDATE);
db2 = Database.open("foo", ObjectStore.UPDATE);
In the Java interface to ObjectStore, the expression db1 == db2 returns true. They refer to the same database object. Consequently, a call to db1.close() or db2.close() closes the same database. No matter how many times you open a database, a single call to the close() method closes the database.

This is different in the C++ interface to ObjectStore. In that interface, for example, if you call open() four times and close() three times all on the same database, the database is still open.

Environment Variables

ObjectStore includes the following environment variables:



[previous] [next]

Copyright © 1998 Object Design, Inc. All rights reserved.

Updated: 10/07/98 08:46:27