Friday, April 25, 2008

OracleAS Java Object Cache

by Fábio Souza

Before I start, I would like to say that many texts and observations were took from the Oracle® Containers for J2EE Services Guide 10g (10.1.3.1.0), Chapter 7.

What is it?
The Java Object Cache (JOC) is a OC4J service that makes cache (in memory or disk) of any kind of Java object.
Use it:
  • To store frequently used objects.

  • To store objects that are costly to create/retrieve.

  • To share objects between applications.
Characteristics:
  • By default, cached objects are stored in memory but disk persistence can be configured.

  • By default, cached objects are local. The DISTRIBUTE mode can be configured.

  • By default, the "write lock" is disabled.

  • Cached objects doesn't have a "read lock".

  • Cached objects are represented by name spaces.

  • Cached objects are invalidated based on time or an explicit request.

  • Cached objects can be invalidated by group or individually.

  • Each cached Java object has a set of associated attributes that control how the object is loaded into the cache, where the object is stored, and how the object is invalidated.

  • When an object is invalidated or updated, the invalid version of the object remains in the cache as long as there are references to that particular version of the object.
The Java Object Cache organization:
  • Cache Environment. The cache environment includes cache regions, subregions, groups, and attributes. Cache regions, subregions, and groups associate objects and collections of objects. Attributes are associated with cache regions, subregions, groups, and individual objects. Attributes affect how the Java Object Cache manages objects.

  • Cache Object Types. The cache object types include memory objects, disk objects, pooled objects, and StreamAccess objects.

    This table gives a little description about the cache environment and the object types.
JOC API:
Only "cache.jar" must be added to application's classpath to start with JOC API. This archive is located in $ORACLE_HOME\javacache\lib\cache.jar. A project in JDeveloper just needs to import the built-in "Java Cache" library.
Distributed Cache Characteristics:
  • The cache management is not centralized. Cache updates and invalidation are propagated to all application server nodes.

  • The OAS' JOC configuration of each OAS node is not propagated to other nodes.

  • The distributed objects must be in the same namespace in each node.
Distributed Cache Configurations:
There are two ways to configure JOC: programmatically (using the oracle.ias.cache.Cache class) and through the javacache.xml file. This post only covers javacache.xml.

Configuring the javacache.xml file:

The javacache.xml localization is specified in the server.xml OC4J configuration file (tag: "<javacache-config>"). To work with javacache.xml it is necessary to start the container with the -Doracle.ias.jcache=true property.
To run JOC in "distributed mode", <communication> tag must be configured in the javacache.xml file like the example below:

<communication>
   <!-- "isdistributed" must be "true" to JOC work with DISTRIBUTE marked objects -->
   <isdistributed>true</isdistributed>
   <!-- Each JOC node must have a "discoverer" element to itself. -->
   <discoverer ip="192.168.0.2" port="7000">
   <discoverer ip="192.168.0.3" port="7000">
</communication>

Configuring the distributed cached objects:
There are three important attributes that we can use when configuring objects:
  • DISTRIBUTE: used to mark objects that will be shared between applications.

  • SYNCHRONIZE: used to allow "write locking" on cached objects.

  • SYNCHRONIZE_DEFAULT: the same use as the SYNCHRONIZE attribute, but it can mark only a region or a group. When they are marked, all objects will be allowed to use the write lock.
The example below shows a distributed cache utilization:

public void defineRegions() {
   // Creating attributes
   Attributes remoteRegionAttr = new Attributes();

   /*
    * Extracted from Attributes.setFlags() javadoc:
    * specifies which of the listed attributes should be set in the Attributes object.
    * The flags may be OR'ed together, i.e., Attributes.DISTRIBUTE | Attributes.SPOOL.
    * Any previous settings will be disregard.
    */
   remoteRegionAttr.setFlags(Attributes.SYNCHRONIZE_DEFAULT | Attributes.DISTRIBUTE);

   try {
      // Creates the "RemoteRegion" with the specified attributes.
      CacheAccess.defineRegion("RemoteRegion", remoteRegionAttr);
   } catch (Exception e) {
      e.printStackTrace();
   }
}

public void createCachedObject(Object object) {
   CacheAccess access = null;
   try {
      // Accessing the created region.
      access = CacheAccess.getAccess("RemoteRegion");
      // Owning cache object's write lock (5 seconds to timeout)
      access.getOwnership("CachedObject", 5000);
      // Caching the object
      access.put("CachedObject", object);
      // Releasing cache object's write lock (5 seconds to timeout)
      access.releaseOwnership("CachedObject", 5000);
   } catch (Exception ex) {
      ex.printStackTrace();
   } finally {
      if (access != null) {
         /*
          * Extracted from CacheAccess.close() javadoc:
          * releases the resource used by current CacheAccess object.
          * Application is required to make this call when it no longer need this CacheAccess instance.
          */
         access.close();
      }
   }
}

public Object retrieveCachedObject() {
   Object object = null;
   CacheAccess access = null;
   try {
      access = CacheAccess.getAccess("RemoteRegion"); // Accessing the created region.
      /*
       * Extracted from CacheAccess.get() javadoc:
       * returns a reference to the object associated with name.
       * If the object is not currently in the cache and a loader object has been
       * registered to the name of the object, then the object will be loaded into
       * the cache. If a loader object has not been defined for this name, then, for
       * DISTRIBUTE object, the default load method will do a netSearch for the object
       * to see if the object exist in any other cache in the system; for a non
       * DISTRIBUTE object, an ObjectNotFoundException will be thrown. The object
       * returned by get is always a reference to a shared object. Get will always
       * return the latest version of the object. A CacheAccess object will only
       * maintain a reference to one cached object at any given time. If get is called
       * multiple times, the object accessed previously will be released.
       */
      object = access.get("CachedObject");
      // It's not necessary to own the writing lock to read cached objects.
   } catch (Exception ex) {
      ex.printStackTrace();
   } finally {
      if (access != null) {
         /*
          * Extracted from CacheAccess.close() javadoc:
          * releases the resource used by current CacheAccess object.
          * Application is required to make this call when it no longer need this CacheAccess instance.
          */
         access.close();
      }
   }
   return object;
}


Concurrent access sensible applications may need read and write synchronism. Because JOC only offers write lock, I had to "force" read lock. This is the solution I found:

/**
 * This method reads and updates a cached object forcing synchronizing both to
 * reading and writing.
 * If it is being executed in multiple threads, each thread will only be able to
 * read the object when another thread release it's writing lock. The writing lock will
 * be released after a thread finishes it's reading and writing.
 */
public void synchronizedReadUpdateCachedObject() {
   CacheAccess access = null;
   try {
      access = CacheAccess.getAccess("RemoteRegion");
      access.getOwnership("CachedObject", 5000);
      Object object = access.get("CachedObject");
      System.out.println(object.toString());
      Object newCachedObject = "New Cached Object";
      // This is the method used to update cached objects.
      access.replace("CachedObject", newCachedObject);
      access.releaseOwnership("CachedObject", 5000);
   } catch (Exception ex) {
      ex.printStackTrace();
   } finally {
      if (access != null) {
         access.close();
      }
   }
}

Declarative Cache:
The JOC offers a way to configure its regions, subregions, groups and objects using XMLs. To use this feature the "preload-file" tag must be added to the javacache.xml. This tag will point to the XML with the environment definitions. More information at: http://http//download.oracle.com/docs/cd/B32110_01/web.1013/b28958/joc.htm#i1085809

CacheWatchUtil:
By default, the Cache Service provides the CacheWatchUtil cache monitoring utility that can display current caches in the system, display a list of cached objects, display caches' attributes, reset cache logger severity, dump cache contents to the log, and so on. It depends on the "dms"library. To execute:

java -classpath $ORACLE_HOME\lib\dms.jar;$ORACLE_HOME\javacache\lib\cache.jar oracle.ias.cache.CacheWatchUtil -config=<path_to_javacache.xml>
More Oracle Application Server 10g caches (entirely took from OC4J documentation):
  • Oracle Application Server Web Cache. The Web Cache sits in front of the application servers (Web servers), caching their content and providing that content to Web browsers that request it. When browsers access the Web site, they send HTTP requests to the Web Cache. The Web Cache, in turn, acts as a virtual server to the application servers. If the requested content has changed, the Web Cache retrieves the new content from the application servers.

    The Web Cache is an HTTP-level cache, maintained outside the application, providing fast cache operations. It is a pure, content-based cache, capable of caching static data (such as HTML, GIF, or JPEG files) or dynamic data (such as servlet or JSP results). Given that it exists as a flat content-based cache outside the application, it cannot cache objects (such as Java objects or XML DOM—Document Object Model—objects) in a structured format. In addition, it offers relatively limited postprocessing abilities on cached data.

  • Web Object Cache. The Web Object Cache is a Web-application-level caching facility. It is an application-level cache, embedded and maintained within a Java Web application. The Web Object Cache is a hybrid cache, both Web-based and object-based. Using the Web Object Cache, applications can cache programmatically, using application programming interface (API) calls (for servlets) or custom tag libraries (for JSPs). The Web Object Cache is generally used as a complement to the Web cache. By default, the Web Object Cache uses the Java Object Cache as its repository.

    A custom tag library or API enables you to define page fragment boundaries and to capture, store, reuse, process, and manage the intermediate and partial execution results of JSP pages and servlets as cached objects. Each block can produce its own resulting cache object. The cached objects can be HTML or XML text fragments, XML DOM objects, or Java serializable objects. These objects can be cached conveniently in association with HTTP semantics. Alternatively, they can be reused outside HTTP, such as in outputting cached XML objects through Simple Mail Transfer Protocol (SMTP), Java Message Service (JMS), Advanced Queueing (AQ), or Simple Object Access Protocol (SOAP).
References:
Oracle Application Server Containers for J2EE Services Guide 10g Release 3 (10.1.3)
Oracle Application Server 10g (10.1.2) JOC Tutorial
Oracle Application Server 10g (10.1.2) JOC javadoc

2 comments:

Anonymous said...

Hi,

Are you sure that the following is the solution for read lock !!?? I though there is no lock on object read....


/**
* This method reads and updates a cached object forcing synchronizing both to
* reading and writing.
* If it is being executed in multiple threads, each thread will only be able to
* read the object when another thread release it's writing lock. The writing lock will
* be released after a thread finishes it's reading and writing.
*/
public void synchronizedReadUpdateCachedObject() {
CacheAccess access = null;
try {
access = CacheAccess.getAccess("RemoteRegion");
access.getOwnership("CachedObject", 5000);
Object object = access.get("CachedObject");
System.out.println(object.toString());
Object newCachedObject = "New Cached Object";
// This is the method used to update cached objects.
access.replace("CachedObject", newCachedObject);
access.releaseOwnership("CachedObject", 5000);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (access != null) {
access.close();
}
}
}

Fábio Saraiva de Souza said...

Yes, you are right, there is no read lock. The point is, if you assure that before every "reading point" you are obtaining the write lock(getOwnership), than you have a read lock. Of course there is a trade-off: synchronism vs performance