Oracle8i Java Developer's Guide
Release 8.1.5

A64682-01

Library

Product

Contents

Index

Prev Next

4
JServer Environment Details

This section further explains the JServer environment, concentrating on Oracle-specific details of Java usage. It focuses on areas you might find to be different from client development and deployment environments. In addition, this chapter covers information you can use to make your Java applications more scalable.

Initializing and Configuring a Java-Enabled Database

The seed database that the typical Oracle installation installs is already Java-enabled; it is ready to run Java stored procedures, JDBC, SQLJ, and CORBA/EJB objects. If you are using you own scripts to create your Oracle instance, then you must initialize the JServer explicitly.

To initialize the JServer manually, execute the SQL script initjvm.sql in ORACLE_HOME/javavm/install. This script loads the initial set of Java classes necessary to support Java, initializes the SQL tables for the CORBA namespace, and publishes some top-level entry points through call-specifications. The initjvm.sql script loads around 4,000 Java classes into the database. As you can tell, much of the JServer is written in Java. These initial set of classes include:

Expect the initjvm.sql script to take around ten minutes to run on most machines when initially setting up a non-Java-enabled database. It loads the classes to the SYS schema and creates public synonyms for them to be accessible to all users. The initjvm.sql script alters some of these classes to run with Definer's rights to support CORBA callouts. Refer to the Oracle8i Java Stored Procedures Developer's Guide for information about Definer's rights and Java classes.

The set of initial Java classes are actually loaded from the Oracle-proprietary-format file classes.bin in ORACLE_HOME/javavm/admin. The proprietary format is designed to optimize the performance when loading well known system classes en masse. In addition, these classes are available in standard Java zip format as aurora.zip in ORACLE_HOME/javavm/lib.

Initializing a Java-enabled database requires a SHARED_POOL_SIZE of about 50MB and enough rollback segments. If the script fails for some reason, such as a lack of resources, then you can adjust resources as necessary and re-execute initjvm.sql. Refer to the /javavm/README.txt file for the most up-to-date information on init.ora configuration parameters and requirements.

In addition, there are specific requirements for enabling EJB and CORBA communications. The initial settings that the typical and custom JServer installations furnish should be sufficient to get you started. Consult the specifics of the documentation in the Oracle8i Enterprise JavaBeans and CORBA Developer's Guide for more details.

Package DBMS_JAVA

When initializing the JServer, the initjvm.sql script creates the PL/SQL package DBMS_JAVA. Some entrypoints of DBMS_JAVA are for your use; others are currently for internal use only. The corresponding Java class DbmsJava provides a "grab bag" for accessing RDBMS functionality from Java. As such, many of its static methods are also published through call specifications and made available in the DBMS_JAVA package. The DBMS_JAVA package provides the following entrypoints:

FUNCTION longname (shortname VARCHAR2) RETURN VARCHAR2

Return the longname from a Java schema object. Because Java classes and methods can have names exceeding the maximum SQL identifier length, Aurora uses abbreviated names internally for SQL access. This function simply returns the original Java name for any (potentially) truncated name. An example of this function is to print the fully qualified name of classes that are invalid for some reason:

select dbms_java.longname (object_name) from user_objects 
   where object_type = 'JAVA CLASS' and status = 'INVALID';

Refer to the Oracle8i Java Stored Procedures Developer's Guide for a detailed example of the use of this function and ways to determine which Java schema objects are present on the server.

FUNCTION get_compiler_option(what VARCHAR2, optionName VARCHAR2)

PROCEDURE set_compiler_option(what VARCHAR2, optionName VARCHAR2, value VARCHAR2)

PROCEDURE reset_compiler_option(what VARCHAR2, optionName VARCHAR2)

These three entry points control the options of the JServer Java and SQLJ compiler Oracle8i delivers. Both the Oracle8i Java Stored Procedures Developer's Guide and Oracle8i SQLJ Developer's Guide and Reference document the options and these entry points.

PROCEDURE set_output (buffersize NUMBER)

This procedure redirects the output of Java stored procedures and triggers to the DBMS_OUTPUT package. See the discussion in "Redirecting Output On the Server" in this chapter for an example.

Security

As a fully-compliant virtual machine implementation, JServer supports standard Java security mechanisms. At initialization time, Aurora installs an instance of java.lang.SecurityManager, and the JLS specifies that you cannot install another instance. Most of the policies the installed Aurora security manager enforces depend on the dynamic id, which is the same Oracle user that determines which SQL operations Java classes can perform. The way in which the dynamic id is determined depends on the kind of session you are in. Generally, the dynamic id is simply the session owner, but special rules are followed for CORBA and EJB. You can change the dynamic id by invoking methods of classes with Definer's rights or by certain direct operations.

In most cases, the SecurityManager checks whether the dynamic id has been granted JAVASYSPRIV, which is an ordinary database ROLE Aurora creates when you install Java. You can grant and revoke JAVASYSPRIV just as you would for any other ROLE. Some operations require you to grant only JAVAUSERPRIV. For example, creating a file requires JAVASYSPRIV, but reading and writing requires only JAVAUSERPRIV. In most cases, if the user executing the code in question (the dynamic id) has not been granted JAVASYSPRIV and that user attempts to perform a disallowed operation, then a java.lang.SecurityException will be thrown. The next section, "Security Manager", discusses the details of which ROLES are defined and which operations they restrict.

Security Manager

The following table lists the checks performed by the SecurityManager in place in the Aurora virtual machine. Generally, SecurityManager allows without restriction operations that affect only the current session. Operations that interact with the external world in ordinary ways allowed in SQL operations require JAVAUSERPRIV, which allows you to execute JServer-resident Java with about the same restrictions, or lack of restrictions, that Sun's JDK imposes. Because your code will be executing on a server that may be providing mission critical functionality for many users, Oracle advises you to carefully consider how you grant privileges. In particular, providing access to the server-side file system or other O/S resources poses a security and data integrity risk.

The following table lists the check methods of java.lang.SecurityManager and indicates which roles are required. If you invoke one of these methods without the required role, then it will throw a SecurityException. In all but one case, the check is performed on the dynamic id; in only one case, the check is performed on the static id. That is, the class calling checkLink must be loaded in a schema granted the indicated privilege.

Check Methods   Roles  
checkAccess   none  
checkAwtEventQueueAccess   none  
checkConnect   JAVAUSERPRIV  
checkCreateClassLoader   none  
checkDelete   see IO  
checkExec   JAVASYSPRIV  
checkExit   none  
checkLink   JAVASYSPRIV(static id)  
checkListen   JAVASYSPRIV  
checkMemberAccess   none  
checkMulticast   none  
checkPackageAccess   none  
checkPrintJobAccess   none  
checkPropertiesAccess   none  
checkPropertyAccess   none  
checkRead   see IO  
checkSecurityAccess   none  
checkSetFactory   JAVASYSPRIV  
checkSystemClipboardAccess   none  
checkTopLevelWindow   none  
checkWrite   see IO  

I/O Operations

The policy for checking I/O operations has two parts:

  1. If the dynamic id has JAVASYSPRIV, then the SecurityManager will allow the operation to proceed.

  2. If the dynamic id has JAVAUSERPRIV, then the SecurityManager allows it, subject to the same rules that apply to the PL/SQL FILE_IO package. Specifically, the file must be in a directory (or subdirectory) the utl_file_dir parameter specifies.

Debugging

Refer to the discussion in the section "Debugging in the JServer" for information on using the prerelease debugging facilities in this release.

To invoke the debug agent, the caller must have JAVADEBUGPRIV, which implies JAVASYSPRIV. Although you should be able to debug your own code without JAVASYSPRIV, this restriction was left in place for this early-access version of the debugger. A debugger provides extensive access to both code and data on the server, but at this time, we envision its use to be restricted to development environments.

Protected Packages

You cannot replace system classes (those that CREATE JAVA SYSTEM installs, which you invoke with initjvm.sql at database initialization time). In addition, classes in any package (or sub-package) in which system classes reside, can be created only in the SYS schema. Protected packages include those starting with the patterns:

java
javax
oracle.aurora
oracle.jdbc
oracle.rdbms
oracle.sqlj
oracle.sqlnet
oracle.vm
oracle.weak
sun.tools
sun.misc
sun.net
com.visigenic.vbroker
com.sun.jndi
org.omg
sqlj

Natively Compiled Code

Oracle delivers the JServer with all core Java class libraries and Oracle-provided Java code natively compiled for greater execution speed. Java classes exist as shared libraries in the /javavm/admin directory below ORACLE_HOME. Each shared library corresponds to a Java package; for example, libjox8java_lang.so on Solaris and libjox8java_lang.dll on Windows/NT hold java.lang classes. Specifics of packaging and naming may vary by platform. The Aurora virtual machine uses these files internally and opens them, as necessary, at runtime.

Native compilation provides a speed increase ranging from two to ten times bytecode interpretation. The exact speed increase is dependent on a number of factors, including:

In general, natively compiled code consumes more memory than interpreted code by a factor of two to three. Caching in an adaptive optimization technique produces a similar trade-off. This is particularly true when a Java server is executing independent code or stored procedures from thousands of users. Using the JServer's way-ahead-of-time approach for delivering natively compiled Java code provides a large, consistent performance gain regardless of the number of users or the code paths they traverse on the server.

Java native compilation technology will be available for your Java code in subsequent releases. In the current JServer release, Java code you load to the server is interpreted, while the underlying core classes upon which your code relies (java.lang.*) are fully compiled. Until the native compiler is available for user programs, the net speed benefit of native compilation to your executing program is, therefore, dependent upon how much native code is traversed, as opposed to interpreted code. The more Java code from core classes and Oracle-provided class libraries you use, the more benefit you will see from native compilation.

Java Memory Usage and Database Configuration

The typical and custom database installation process furnishes a database that has been configured for reasonable Java usage during development. It is important to realize that runtime use of Java is generally deterministic in its usage of system resources for a given deployed application, while resources you use during development vary widely depending on what you will be doing.

The primary init.ora parameters that can affect Java usage and performance are shared_pool_size and java_pool_size.

The memory specified in shared_pool_size is consumed transiently when you use loadjava. The database initialization process (executing initjvm.sql against a clean database, as opposed to the installed seed database) requires shared_pool_size to be set to 50MB as it loads the Java binaries for more than 4,000 classes and resolves them. Shared_pool_size resource is also consumed when you create call specifications and as the system tracks dynamically loaded Java classes at runtime.

Aurora's memory manager allocates all other Java state during runtime execution from the amount of memory allocated using java_pool_size. This memory includes the shared in-memory representation of Java method and class definitions, as well as the Java objects that are migrated to session space at end-of-call. In the former case, you will be sharing the memory cost with all Java users. In the latter case, under MTS, you must adjust java_pool_size allocation based on the actual amount of state held in static variables for each session.

You can adjust two other init.ora parameters that relate to Java:

  1. java_soft_sessionspace_limit--When a user's session-duration Java state exceeds this size, Aurora generates a warning that goes into the RDBMS .trc file. The default is 1MB. You should understand the memory requirements of your deployed applications, especially as they relate to usage of session space. This parameter allows you to specify a "soft limit" on Java memory usage in a session, as a means to warn you if something is awry.

  2. java_max_sessionspace_size--When a user's session-duration Java state attempts to exceeds this size, Aurora kills the session with an out-of-memory failure. The default is 4GB. This limit is purposely set extremely high to be normally invisible. If a user-invokable Java program executing in the server can be used in a way that is not self-limiting in its memory usage, then this setting may be useful to place a hard limit on the amount of session space made available to it.

JServer's unique memory management facilities and sharing of read-only artifacts (such as bytecodes) enables HelloWorld to execute with a per-session incremental memory requirement of only 35K bytes. More stateful server applications, such as the Aurora/ORB that CORBA and EJB applications use, have a per-session incremental memory requirement of approximately 200KB. Such applications need to retain a significant amount of state in static variables across multiple calls. Refer to the discussion in the following section, "End-of-Call Migration", for more information on understanding and controlling migration of static variables at end-of-call.

End-of-Call Migration

To maximize the number of users who can execute your Java program at the same time, it is important to minimize the footprint of a session. In particular, to achieve maximum scalability, an inactive session should take up as little memory space as possible. A simple technique to minimize footprint is to release large data structures at end-of-call. You can lazily recreate many data structures when you need them again in another call. For this reason, the Aurora Java virtual machine has a mechanism for calling a specified Java method when a session is about to become inactive, such as at end-of-call time.

The decision of whether to null-out data structures at end-of-call and then recreate them for each new call is a typical time/space trade-off. There is some extra time in recreating the structure, but you may save significant space by not holding on to the structure between calls. In addition, there is a time consideration in that objects (especially large objects) are more expensive to access after they have been migrated to session space. The penalty results from the differences in representation of session vs. call-space based objects.

Examples of data structures that are candidates for this type of optimization include:

The last item can be tricky and complicate your code, making it hard to maintain, so you should consider it only after demonstrating that the space saved will be worth the effort.

Aurora-Specific Support for End-of-Call Optimization

Oracle furnishes support for implementing end-of-call optimizations in the oracle.aurora.memoryManager package--the EndOfCallRegistry class, and the Callback interface.

Class oracle.aurora.memoryManager.EndOfCallRegistry
and the Callback Interface

This class maintains a table of thunk/value pairs known as end-of-call callbacks. At the end of a call, Aurora invokes thunk.act(value) for every thunk/value pair in the table. After the callback is invoked, it is removed from the table. If the end of the call is also the end of the session, then callbacks are not invoked.

It is important to note that a weak table holds end-of-call callbacks. If either the thunk or value are not otherwise reachable (see JLS section 12.6) from the Java program, then they will both be dropped from the table. The use of a weak table to hold callbacks also means that registering a callback will not prevent the garbage collector from reclaiming that object. Therefore, you must "hold on" to the callback yourself if you need it--you cannot rely on the table holding it back.

The way you use the EndOfCallRegistry will depend on whether you are dealing with objects held in static fields or instance fields. For a static field, you will be using the EndOfCallRegistry to clear state associated with an entire class. In this case, the thunk should be held in a private static field, and any code that requires access to the cached data dropped between calls must go through an accessor method that lazily creates (or recreates) the cached data. Here is an example:

import oracle.aurora.memoryManager.Callback;
import oracle.aurora.memoryManager.EndOfCallRegistry;

class Example {
   static Object cachedField = null;
   private static Callback thunk = null;
   static void clearCachedField() {
   // clear out both the cached field, and the thunk so they don't
   // silt up session space
   cachedField = null;
   thunk = null;
}

private static Object getCachedField() {
   if (cachedField == null) {
      // save thunk in static field so it doesn't get reclaimed
      // by garbage colector
      thunk =
         new Callback () {
               public void act(Object obj) {
                     Example.clearCachedField();
         }
      };
      // register thunk to clear cachedField at end of call.
      EndOfCallRegistry.registerCallback(thunk);
      // finally, set cached field
      cachedField = createCachedField();
   }
   return cachedField;
}

private static Object createCachedField() {
....
}
}

You may also want to use the EndOfCallRegistry to clear state in data structures held in instance fields; for example, state associated with each instance of a class. In this case, each instance has a field holding the cached state for the instance and fills in the cached field as necessary. Access to the cached field is with an accessor method that ensures the state is encached. The class implements the Callback interface and registers the instance when the cached field gets filled in. This approach ensures that the lifetime of the callback thunk is identical to the lifetime of the instance because they are the same object.

import oracle.aurora.memoryManager.Callback;
import oracle.aurora.memoryManager.EndOfCallRegistry;

class Example2 implements Callback {
   private Object cachedField = null;
   public void act(Object obj) {
   // clear cached field
   cachedField = null;
}

// our accessor method
private static Object getCachedField() {
   if (cachedField == null) {
      // if cachedField is not filled in then we need to
      // register self, and fill it in.
      EndOfCallRegistry.registerCallback(self);
      cachedField = createCachedField();
   }
   return cachedField;
}

private Object createCachedField() {
....
}
}

You may certainly find other ways in which end-of-call notification will be useful to your applications.

public static void registerCallback(Callback thunk, Object value)

This method installs thunk as an end of call callback. At end of call, the Aurora virtual machine arranges that thunk.act(value) will be called for every end of call callback. In some cases, value is necessary to hold state the callback will need to get its job done. Most users will not need to specify a value.

public static void registerCallback(Callback thunk)

This is a shorthand method equivalent to registerCallback(thunk, thunk). Most users will register a callback in this way.

static void runCallbacks()

The virtual machine calls this method at end-of-call and calls thunk.act(value) for every thunk/value you register using registerCallback. You should never call this method in your code. It is called at end of call before object migration and before the last finalization step.

Interface oracle.aurora.memoryManager.Callback

An object you want to register using EndOfCallRegistry.registerCallback must implement this interface. Although this interface was implemented to serve the purposes of EndOfCallRegistry, it may be useful in your application where you require notification of end-of-call.

public void act(Object value)

You can write this method to do whatever is appropriate for the particular purposes of the callback.

Threads

This section describes the Oracle Aurora Java virtual machine threading model. It discusses the important distinctions between the thread model the Java Language Specification describes, Oracle's implementation of Java language-level threads, and concurrent sessions running in the JServer.

The Java Language Thread Model

The Java Language Specification (JLS) describes two different thread models--cooperative and preemptive. A JLS-compliant Java virtual machine can implement either of these two models. Both have advantages and disadvantages.

To allow for flexibility in virtual machine implementations, certain aspects of the thread model are purposely vague in the JLS, such as when a thread can be interrupted and what operations may cause a thread to block.

Cooperative Threading

In a cooperative threading model, the Java virtual machine runs all Java threads on a single operating system thread, scheduling them in a round-robin fashion and switching between them only when they invoke the Thread.yield() method or when they invoke a service that blocks until ready, such as a wait on a network socket.

This thread model is portable and simple to program; it is also efficient to implement in the virtual machine because a thread switch does not require any system calls. It is also safer, as the virtual machine can detect a deadlock that would hang a preemptive virtual machine and can then raise a runtime exception. However, this thread model does not exhibit significant concurrency and will not take advantage of SMP hardware.

Preemptive Threading

In a preemptive model, the virtual machine assigns each Java thread to a separate operating system thread. Preemptive threads are scheduled by the operating system and provide better concurrency and throughput than cooperative threads. (This assumes that the operating system provides for real concurrency).

With preemptive threads, multiple concurrent operations can be outstanding, and the programmer need not be concerned with manually determining and coding yield points; but threaded programs are tricky to write, easy to deadlock, and amazingly difficult to debug in practice. A preemptive virtual machine cannot detect deadlock but can take advantage of SMP hardware.

True Concurrency

There is an added consideration for preemptive threads when the hardware supports multiple processors, and the operating system supports symmetric multiprocessing. In this case, the virtual machine can assign preemptive threads to separate processors, resulting in true concurrency. The advantage of this is that work is actually done in parallel on the separate processors; the disadvantage is that deadlock and concurrency errors are far more likely and are extraordinarily subtle and difficult to debug.

Java in the Server

A typical use of Java in the server is to create middle-tier application logic between the client application front-end and the database back-end. A server implemented fully in Java would require the application programmer to write both the application logic and the server logic to support it. The server logic that implements the socket listeners, thread scheduling, transaction model, and database access dominates the cost of the server development and dwarfs the actual application logic. Moreover, the same server logic is often duplicated over multiple server applications.

A major problem with implementing your own Java server is Java's lack of scalability. Because of the interpreted nature of Java bytecodes and the large memory footprint of the typical Java virtual machine, it is possible to run only a handful of simultaneous transactions before overwhelming the supporting hardware.

Java's thread-safe libraries also detract from the scalability of Java servers. Because most calls to Java collection classes are synchronized, regardless of whether the program actually uses multiple threads, there is significant overhead in the normal Java virtual machine for each call to a synchronized object. In a preemptive virtual machine, such synchronized calls require several expensive calls to operating system services to perform the synchronization.

The Oracle Java Threading Model

The Oracle server solves both the speed problem and the memory footprint problem. The Oracle server supports a highly scalable architecture external to the Java virtual machine. In contrast to the 6- to 8-MB memory footprint of the typical Java virtual machine, the Oracle server can create thousands of Java virtual machines, with each one taking less than 40KB each. Moreover, because each Java session runs completely independently of other sessions, Aurora uses a platform's SMP capabilities with nearly 100 percent efficiency.

Oracle's native compilation handles the speed problem. The Java server program, as well as all of the supporting Java classes, are compiled to C. The C programs are then compiled into shared libraries and are dynamically linked into the server kernel for execution.

Rather than implementing your own server in Java, the Oracle server provides the scalable framework for your application logic. The Oracle Java virtual machine complies with the JLS by supporting the cooperative threading model. Although this may seem to be disadvantageous, there is no need to use threads within the application logic because the Oracle server preemptively schedules the individual virtual machines. If you must support hundreds or thousands of simultaneous transactions, simply start each one in its own virtual machine. This is exactly what happens when you create a session on the JServer.

The normal transactional capabilities of the Oracle database server accomplish coordination and data transfer between the virtual machines.

The Oracle Thread Synchronization Mechanism

The Oracle Java virtual machine handles synchronized objects in an efficient way without any operating system calls. Every object header contains two bits--the locked bit and the contended bit. In addition, a hash table maps objects to monitor instances.

To acquire a monitor, the thread checks obj.locked. If it is not set, then the thread sets it, allocates a new monitor in the monitor table and continues. If obj.locked is set, then the thread checks whether it is the thread already holding the monitor. If it is, it simply increments a reference count; if not, the thread must sleep until the thread currently holding the monitor exits. Prior to sleeping, the thread can throw a deadlock exception if a circular wait condition occurs. Otherwise, the thread sets obj.contended, modifies its waiting_for slot to point at obj, and goes to sleep.

To release a monitor, the thread first decrements the reference count. If the count is zero, then it clears obj.locked. If obj.contended is set, it then finds all threads waiting for obj, and wakes them up. If no threads are waiting for obj, then obj.contended is cleared. This efficiency is in stark contrast to the numerous system calls that a preemptively Java threaded implementation must make.

Sockets, Remote Method Invocation (RMI), and O/S Resources

Operating system (O/S) resources are a limited commodity on any computer. Because Java is targeted at providing a computing platform, not just a programming language, it contains platform-independent classes and frameworks for accessing extremely platform-specific resources, such as files and sockets. The most basic O/S resource is memory. Aurora's memory manager manages it internally, allocating and freeing memory as you create new objects and as you no longer need them. As discussed elsewhere, automated storage reclamation, or garbage collection, is a key feature of the Java language. The language and class library support no direct means to allocate and free memory. On the other hand, Java contains classes that represent other O/S resources, such as files and sockets. Instances of these classes themselves hold on to operating system constructs, such as file handles, so it is important for you to understand how they interact with JServer's idea of call and session. Java's Remote Method Invocation (RMI) support makes use of Sockets, so we have included some discussion of how call and session also affect it.

Session Lifetime

When you connect to Oracle8i, you start a database session. The first time during this session that you invoke a Java method, a session-private Java virtual machine is created for the session. In reality, the Oracle implementation shares almost all of the code, infrastructure, and metadata of the active virtual machines between users. The appearance to each session, however, is that it has a completely private Java virtual machine. All Java objects created while a Java session is active are private to the session. All information static variables store (and by extension, any Java objects these static variables reference) remain valid for the lifetime of the session. A session ends when one of the following events occurs:

  1. The oracle.aurora.vm.OracleRuntime.exitSession() method is invoked.

  2. The session times out (optional for CORBA/EJB sessions).

  3. The user takes some action outside of Java code to end the database session.

Call Lifetime and Threads

You can invoke Java methods multiple times during a session. Each top level Java method invocation is a call. At the beginning of a call, one Java thread is executing. In most cases, this single thread will be the only thread that is necessary. In the single thread of execution case, the call ends when one of the following events occurs:

  1. The thread returns to its caller.

  2. An exception is thrown and is not caught in Java code.

  3. The System.exit(), oracle.aurora.vm.OracleRuntime.exitCall(), or oracle.aurora.vm.oracleRuntime.exitSession() method is invoked.

If the initial thread creates and starts other Java threads, then the rules about when a call ends are slightly more complicated. In this case, the call ends by one of the following two ways:

  1. The main thread returns to its caller, or an exception is thrown and not caught in this thread, and all other non-daemon threads complete execution. Non-daemon threads complete either by returning from their initial method or because an exception is thrown and not caught in the thread.

  2. Any thread invokes the System.exit(), oracle.aurora.vm.OracleRuntime.exitCall(), or oracle.aurora.vm.oracleRuntime.exitSession() method.

When a call ends because of a return and/or uncaught exceptions, Aurora throws a ThreadDeathException in all daemon threads. The ThreadDeathException essentially forces threads to stop execution.

When a call ends because of a call to System.exit(), oracle.aurora.vm.OracleRuntime.exitCall(), or oracle.aurora.vm.oracleRuntime.exitSession(), Aurora ends the call abruptly, terminating all threads, and throwing no ThreadDeathExceptions.

Refer to the previous discussion in "Threads" for more background on threading models and the specific implementation used in the Aurora Java virtual machine.

During the execution of a single call, a Java program may recursively cause more Java code to be executed. For example, your program may issue a SQL query using JDBC or SQLJ that in turn causes a trigger written in Java to be invoked. All of the preceding remarks regarding call lifetime apply to the top-most call to Java code, not to the recursive call. For example, a call to System.exit() from within a recursive call will exit the entire top-most call to Java, not just the recursive call.

O/S Resource Access

By default, a Java user has no direct access to most operating system resources. A system administrator may give a Java user access by granting the user JAVASYSPRIV. An alternative way to provide privileged access at a class level of granularity is to create the class using the -definer option to loadjava and specify a schema that has JAVASYSPRIV. Refer to the discussion in "Security" for more information on these roles predefined in the Java-enabled seed database and their use in restricting access to O/S resources.

O/S Resource Lifetime

You access O/S resources using the standard core Java classes and methods. Once you access a resource, the time that it remains active (usable) varies according to the type of resource. The system closes all files left open when a database call ends. As described previously, all threads are, by definition, terminated when a call ends.

Oracle provides socket class libraries to allow you, in certain circumstances, to use sockets across calls. Those circumstances depend somewhat on whether you are communicating with JServer through IIOP (you are using CORBA to communicate between client and server) or through the traditional Oracle TTC protocol. The latter case would apply to normal stored procedures, SQLJ, and JDBC, although of course, you can use SQLJ and JDBC to access SQL data within CORBA and EJB applications.

As described in the Oracle8i Enterprise JavaBeans and CORBA Developer's Guide, IIOP is implemented in JServer using a Presentation. In networking, the presentation layer is responsible for making sure data is represented in a format the application and session layers can accommodate. In Oracle, the term Presentation can refer to a service protocol that accepts incoming network requests and activates routines in the database kernel layer or in the Aurora Java virtual machine to handle the requests.

Earlier versions of the Oracle database server had a single service--the two-task common (TTC) layer. This service handled incoming Net8 requests for database SQL services from Oracle tools (such as SQL*Plus) and customer-written applications (using Forms, Pro*C, or the OCI).

Presentation-based services handle requests over TCP/IP routed to the presentation entrypoint by the listener and dispatcher. The IIOP services Oracle delivers with JServer are capable of starting, controlling, and terminating Oracle8i database sessions in the same way that an incoming TTC request from a tool such as SQL*Plus is capable of starting and terminating a database session.

Presentations form the basis for support of many additional protocols beyond IIOP. The Web server discussed in the section "Http Protocol Support and Web Server Demonstration" uses Presentations to support http protocol. In the current JServer release, Presentations are not available for your use. However, because they are implemented completely in Java, it is possible to allow user-defined protocols to communicate with JServer. For purposes of this discussion, however, it is important to understand that in the current JServer release, support for sockets that can be used across calls is available only when writing Java programs invoked through IIOP.

Sockets are created in one of the following ways:

Aurora uses the RDBMS virtual circuit facility for both client and server Sockets and VirtualCircuits when you execute Java code inside of a Presentation. Outside of a Presentation, Sockets use the underlying operating system sockets directly. You can only use VirtualCircuits inside of Presentations. ServerSockets use operating system sockets directly, both inside and outside of a Presentation.

Regardless of how you implement them, client sockets and all virtual circuits remain open until the session terminates. When the session is running in a dedicated server process, ServerSockets also remain open until the end of a session. However, because Presentations do not use dedicated processes, ServerSockets you create when running in a Presentation are closed at the end of a database call. Oracle encourages implementors to use server virtual circuits rather than ServerSockets when running inside of Presentations. Server virtual circuits remain open as long as you desire in a session. You should make direct use of operating system sockets only where scalability is not of concern. The Oracle8i server's ability to service many client requests is an integral part of VirtualCircuit support. Therefore, Oracle encourages developers to use Presentations to write Java code that functions as a server. Refer to the "Challenges In Developing a Scalable Java Platform" section in Chapter 2, "Java Platform for the Enterprise", for more information on the difference between writing a server in Java, as opposed to executing Java code in a server.

Java Objects as Resources

Each O/S resource the preceding section describes (files, threads, and sockets) has a Java class that represents it. Regardless of the usable lifetime of the object, the Java object associated with it is potentially valid for the duration of the session. This can occur, for example, if the Java object is stored in a static class variable or a class variable references it directly or indirectly. If you attempt to use one these Java objects after its usable lifetime is over, then Aurora throws an exception. For example, if an attempt is made to read from a java.io.FileInputStream that was closed at the end of a previous call, then java.io.IOException is thrown. Similarly, java.lang.Thread.isAlive() will be false for any Thread object running in a previous call and still accessible in a subsequent call.

Remote Method Invocation

JServer fully supports Java Remote Method Invocation (RMI). All RMI classes and java.net support are in place. In general, however, RMI is not useful or scalable in JServer applications. CORBA and EJB are the preferred means to invoke methods of remote objects. The RMI Server that Sun supplies does function on the JServer platform. However, because it uses operating system sockets and is not accessible through a Presentation, it is useful only within the context of a single call. It relies heavily on Java language level threads. By contrast, the Aurora/ORB and EJB rely on the Oracle8i server to gain scalability. Refer to the section "Challenges In Developing a Scalable Java Platform" in Chapter 2, "Java Platform for the Enterprise", for more information and to the section "Threads" in this chapter. Of course, you could efficiently implement an RMI server as a presentation, and certainly nothing prevents JServer customers from doing so; however, this purpose seems already reasonably well served by CORBA and EJB.

Java Native Interface (JNI) Support

The Java Native Interface (JNI) is a standard programming interface for writing Java native methods and embedding the Java virtual machine into native applications. The primary goal of JNI is to provide binary compatibility of native method libraries across all Java virtual machine implementations on a given platform. JNI was added to Java with the 1.1 release.

Oracle does not support the use of JNI in JServer applications. If you use JNI, then your application will not be 100% pure Java, and the native methods will require porting between platforms. Native methods have the potential for crashing the server, violating security, and even corrupting data.

Debugging Server Applications

JServer furnishes debugging capability in a pre-release form but does not formally support it. The debugging capability is useful for developers who can use the JDK's jdb debugger for debugging server-side code. Independent IDE vendors, as well as Oracle's JDeveloper, will be able to integrate their own debuggers with JServer using the capabilities present in the current release.

A key goal of Oracle8i is adherence to open Internet and Java standards. In keeping with that goal, the Aurora virtual machine includes an implementation of the sun.tools.debug.Agent Java debugging protocol. Note that some of the awkward aspects of usage of the pre-release debugging capabilities are due to our adherence to the limited capabilities available through this Sun-provided protocol. Vendors may implement extensions to the basic protocol. All such extensions available in the JServer environment Oracle provides will be publicly specified for use by all vendors.

Overview

In a non-client-server environment, you would be debugging a Java application executing on a virtual machine resident on your own workstation. Sun's jdb debugger operates on this principal. The debugger communicates with the executing virtual machine through a protocol. Both the debugger and the virtual machine understand the protocol, allowing you to display information about the state of the running Java program in the debugger. In the JServer, your Java program executes on a server. The server may reside on the same physical machine, but it will typically reside on a separate machine from the one on which you are executing the debugger.

To maintain the illusion that you are executing your local debugger, such as jdb, against a local virtual machine, we provide a DebugProxy Java class. The DebugProxy allows a standard debugger that supports the sun.tools.debug.Agent protocol to connect to it as if the program being debugged resided locally. The proxy forwards to the server the standard requests the protocol supports and returns the results to the debugger.

The steps for debugging are, using jdb as the example:

  1. Start the DebugProxy. The DebugProxy waits for a DebugAgent that executes in the server to make connections.

  2. Connect to the server (starting a session) and boot the DebugAgent on the server. In this prerelease version, there is no way to cause server-resident code to execute and "break"; that is, execute and remain indefinitely in a halted mode. Instead, when you start the DebugAgent, you must specify a timeout period for the DebugAgent to wait before terminating. When the DebugAgent starts, the DebugProxy displays a password to use when connecting a debugger.

  3. Start jdb using the password provided by the DebugProxy when the DebugAgent connected to it. Use jdb to suspend all threads of the Java program executing in the server. Proceed with debugging using jdb facilities.

The remaining discussion in this section provides examples and details of these steps.

Starting the Debug Proxy

The debug proxy is shipped as part of the aurora_client.jar file. In addition, other tools, such as the loadjava utility, are included in this jar file. The DebugProxy class serves as a bridge between Aurora virtual machines running in the Oracle8i server and client-side debuggers running on your workstation.

The first step in server debugging is to start a debug proxy, which will wait for connections from debug agents. Assuming the aurora_client.jar file is part of your CLASSPATH, you would invoke the DebugProxy as:


java DebugProxy 

You can also specify a particular port to wait on:

java DebugProxy -port 666

The proxy prints out its name, its address, and the port it is waiting on:

Proxy Name: yourmachinename
Proxy Address: aaa.bbb.ccc.ddd
Proxy Port: 666

Starting, Stopping, and Restarting the Debug Agent

Once a proxy is running, you can boot a debug agent to connect to the proxy from SQL*Plus. You must specify the IP address or URL for a machine running a debug proxy, the port the proxy is waiting on, and a timeout in seconds:

SQL> call dbms_java.start_debugging('yourmachinename', 666, 66);

The call will wait until the timeout expires before it completes, so give yourself enough time get your debugger running, but not so much as to delay your session if for some reason you cannot connect a debugger.

If an agent is already running, then Aurora stops it and starts a new agent. You can stop the debug agent explicitly as well:

SQL> call dbms_java.stop_debugging(); 

Once a debug agent starts, it runs until you stop it, the debugger disconnects, or the session ends.

You can restart a stopped agent, with any breakpoints still set. The call will wait until the timeout expires before it completes. You can also restart a running agent just to buy some seconds to suspend threads and set breakpoints.

SQL> call dbms_java.restart_debugging(66); 

Note that the preceding dbms_java calls are simply published entry points to static methods that reside in oracle.aurora.debug.OracleAgent class. In addition, you can start, stop, and restart the debug agent in Java code using the class oracle.aurora.debug.OracleAgent directly:

public static void start(
   String host, int port, long timeout_seconds);
public static void stop();
public static void restart(long timeout_seconds);

Connecting a Debugger

Each time a debug agent connects, the proxy starts a thread to wait for connections from a debugger. The thread prints out the number, name and address of the connecting agent, the port it is waiting on, and the port encoded as a password. Here, a specific port and password are provided for illustration only:

Agent Number: 1
Agent Name: servername
Agent Address: eee.fff.jjj.kkk
Agent Port: 2286
Agent Password: 3i65bn

You can then pass the password to a jdb-compatible debugger (JDK 1.1.6 or later):

jdb -password 3i65bn

Probably the first thing you should do in the debugger is suspend all threads. Otherwise, your start_debugging call might time out and complete before you get your breakpoints set.

If your code writes to System.out or System.err, then you may also want to use the dbgtrace flag to jdb, which redirects these streams to the debugging console:

jdb -dbgtrace -password 3i65bn

Just-in-Time" Debugging

Aurora takes any arguments to DebugProxy beyond the optional port as a command to execute. The agent port password is appended to the command. For instance, on Windows/NT, you can cause the proxy to execute jdb in a new console each time an agent connects to the proxy with:

java DebugProxy -port 666 start jdb -password

Redirecting Output on the Server

System.out and System.err print to the current trace files. To redirect output to the SQL*Plus text buffer, use this simple workaround:

SQL> SET SERVEROUTPUT ON
SQL> CALL dbms_java.set_output(2000);

The minimum (and default) buffer size is 2,000 bytes; the maximum size is 1,000,000 bytes. In the following example, the buffer size is increased to 5,000 bytes:

SQL> SET SERVEROUTPUT ON SIZE 5000
SQL> CALL dbms_java.set_output(5000);

Output prints at the end of call.

For more information about SQL*Plus, see the SQL*Plus User's Guide and Reference.

Class.forName() on the JServer

The Java Language Specification provides the following description of Class.forName():

Given the fully-qualified name of a class, this method attempts to locate, load, and link the class. If it succeeds, then a reference to the Class object for the class is returned. If it fails, then a ClassNotFoundException is thrown.

The difference between the JDK implementation and JServer's implementation is tied to the words "attempts to locate". As discussed previously, JServer uses a resolver to define how it locates a class. The JDK uses the set of directory tree roots the environment variable CLASSPATH specifies. When you execute a method that calls Class.forName(), the resolver of the currently executing class (this) is used to locate the class. This behavior is generally convenient and results in what you would normally expect. However, if you specified different resolvers for different classes, you may not get the results you want.

The following Java code demonstrates how you can use the resolver of a specific class to resolve the class you would name in a call to Class.forName(). It is useful because Class.forName() does a lookup using the resolver of the class that calls it. In the JDK, a similar operation would use the class loader associated with another class; however, in the JServer, this does not work because the system class loader loads all classes, even when they use different resolvers.

import oracle.aurora.rdbms.ClassHandle;
import oracle.aurora.rdbms.Handle;
import oracle.aurora.rdbms.Resolver;

public class ForName {

		/**
		* The class that lookups are relative to.
		*/
		Class from ;

		/**
		* The resolver of from
		*/
		Resolver resolver;

		/**
		* Constructor.
		* @param from the Class that lookups should be relative to
		*/
public ForName(Class from) {
   this.from = from;
   ClassHandle fromHandle = ClassHandle.lookup(from) ;
   this.resolver = fromHandle.resolver() ;
   }

/**
* Lookup a name relative to the class supplied to the constructor.
	* Looking up relative to a class means using that class' resolver.
* @param name the fully qualified name of a class to be looked up
* @return the Class. 
* @exception ClassNotFoundException if the lookup fails to find a
* class. 
*/
public Class lookup(String name) throws ClassNotFoundException {
ClassHandle h = Handle.lookupClass(name, resolver);
if ( h == null ) {
   throw new ClassNotFoundException(name + 
            " not found relative to " + from);
      }
      return h.loadClass();
   }


/**
* Lookup the a name relative to the class.
* @param name the fully qualified name of a class to be looked up
* @param from the Class that the lookup is relative to.
* @return the Class found in the lookup. 
* @exception ClassNotFoundException if the lookup fails to find a
* class. 
*/
public static Class lookup(String name, Class from) 
      throws ClassNotFoundException 
   {
      return (new ForName(from)).lookup(name) ;
   }

/**
* A top level main to test ForName. It expects two arguments
* The name to be looked up and the name of a class that the
* lookup is to be relative to.
*/
public static void main(String[] argv) {
      System.out.println("looking up " + argv[0] 
            + " relative to " + argv[1]);
      try {
         Class cl = lookup(argv[0], Class.forName(argv[1]) );
	         ClassHandle h = null ;
         if ( cl != null ) {
            h = ClassHandle.lookup(cl);
         } 
         System.out.println("result is " + h ) ;
      } catch ( ClassNotFoundException ex ) {
            System.out.println(ex.toString());
      }
   }
}

The usage of the ForName class should be evident. To return the class known as "mypackage.myclass" using the resolver of ExistingClass, invoke:

Class myClass = (new ForName (ExistingClass)).lookup("mypackage.myclass")

The sample code also provides a top level main method to test the ForName class.

ByteCode Verifier and Resolvers Containing "-"

According to the Java Virtual Machine Specification, .class files are subject to a process called verification before the class they define is available in a virtual machine. In JServer, the verification process occurs at class resolution. If the verifier determines that the class is malformed, then the verifier does not mark it valid. When the verifier rejects a class, it issues an ORA-29545 error (badly formed class). The loading tool will report the error. In normal use, you will never see this error. It might happen, for example, if the contents of a .class file are not the result of a Java compilation or if the file has been corrupted.

In some situations, the verifier will allow a class to be marked valid but will replace bytecodes in the class to throw an exception at runtime. In these cases, the verifier issues an ORA-29552 (verification warning), which loadjava will report. loadjava issues such a warning when the Java Language Specification would require an IncompatibleClassChangeError be thrown. JServer relies on the verifier to detect these situations, supporting the proper runtime behavior the JLS requires.

Another situation in which the verifier issues a warning is the use of resolvers containing "-". This type of resolver marks your class valid regardless of whether classes it references are present. Because of inheritance and interfaces, you can and often want to write valid Java methods that use an instance of one class as if it were an instance of a superclass or of a specific interface. When the method being verified uses a reference to class A as if it were a reference to class B, the verifier must check that A either extends or implements B. For example, consider the potentially valid method below whose signature implies a return of an instance of B but whose body returns an instance of A:

B myMethod(A a) { return a; }

The method is valid if and only if A extends B or A implements the interface B. If A or B have been resolved using a "-" term, then the verifier does not know that this method is safe. It will replace the bytecodes of myMethod by bytecodes that throw an Exception if myMethod is ever called. Use of other resolvers, for example the default -oracleresolver, ensure that the class definitions of A and B are found and resolved properly if they are present in the schemas they specifically identify. The only time you might consider using the alternative resolver would be if you must load an existing jar file containing classes that reference other non-system classes not included in the jar file. Never use a resolver containing "-" if you later intend to load the classes that were causing you to use such a resolver in the first place. Instead, include all referenced classes in the schema before resolving. Refer to the Oracle8i Java Stored Procedures Developer's Guide for more information and to Appendix A, "Tools", of this Guide for a detailed discussion of resolvers.

How To Tell You Are Executing in the Server

It may be desirable for some reason to write Java code that executes in a certain way in the server and another way on the client. In general, Oracle does not recommend this. In fact, JDBC and SQLJ go to some trouble to allow you to write portable code that avoids this problem, even though the drivers used in the server and client are different.

If you must determine whether your code is executing in the server, then use:

System.getProperty ("oracle.server.version")

It will return a String when running in the server ("8.1.5" for the initial JServer release on Oracle 8i). Otherwise, it will return null.

Http Protocol Support and Web Server Demonstration

The ORACLE_HOME/javavm/demo.tar (or zip) file contains several code samples and demonstrations, including a demonstration Java Web Server running on Oracle8i. This is an example of a forthcoming feature that will allow you to define your own TCP/IP based protocols to communicate with the JServer. When you run the Web Server demo, you are communicating directly with Oracle8i through HTTP, not SQL*Net. Aurora uses the same flexible protocol support infrastructure to deliver IIOP and RMI-over-IIOP for use with the Aurora/ORB and EJB.

The Web Server demonstration runs portions of Sun's Java Web Server backend code, supporting Servlets and Java Server Pages directly within Oracle8i. You can write Servlets that use the JDBC server-side driver to access SQL data. Web browsers connect directly to Oracle8i with absolutely no intervening middle tier and can execute the Servlets and Java Server Pages.

The demonstration Web server uses a crude artifact to tie each Web browser to its own database session. It sends "Keep-Alive" headers in the HTTP replies to ask the Web browser to keep the connection to the server open. This kind of approach contributes to the Web Server's status as a "demo"--not for production use. For the current release, Oracle does not support the Web Server. Additionally, it is worth noting that it is not secure because all Servlets run as SYS.

Please see the README.TXT in the world-o-books directory in demo.tar for more information.




Prev

Next
Oracle
Copyright © 1999 Oracle Corporation.

All Rights Reserved.

Library

Product

Contents

Index