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
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:
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.
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.
osjcfp -dest \osjcfpout -modifyjava java.lang.StringBufferThen 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.
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.
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.
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.
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.
During serialization, none of the transient fields in the IPersistent implementation need to be written out.
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. 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; } ... }
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.
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.
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.
Updated: 10/07/98 08:46:27