Skip to main content

JNI Programming on AIX

Recommendations and tips

Nikolay Yevik, Linux on POWER Technical Consultant, IBM, Software Group
Nikolay Yevik, a Linux on POWER consultant in IBM’s Solutions Enablement group, has more than 5 years of experience working on UNIX platforms, performing development work in C, C++ and Java. He has Masters degrees in Petroleum Engineering and Computer Science.

Summary:  This article provides general guidance for developing Java Native Interface (JNI) applications using IBM JDK for AIX, specifically targeting IBM JDK 1.4.1 for AIX. Specifics of both 32-bit and 64-bit flavors of JDK 1.4.1 are discussed where appropriate. Some generic discussion applies to JNI programming on Java II Platform JDKs from IBM in general. This article is not intended as a JNI API tutorial, but is a brief discussion of the most important specifics of JNI programming on AIX. Knowledge of the JNI specification and full command of the JNI is assumed.

Date:  15 Mar 2004
Level:  Introductory
Activity:  2450 views

Introduction

In this article, routines written in C/C++ are called native functions. The JNI API allows programming in Java without requiring the existing native, possibly legacy, libraries to be entirely rebuilt, including native system libraries. JNI API can mainly be used in two ways:

  • Creating a library that can be called from a Java application
  • Embedding JVM into a C/C++ application and executing Java code with the JNI Invocation API.

Programming with the JNI is generally reserved for experienced programmers who need to take advantage of platform-specific function outside of JVM. Programming with JNI API on AIX requires expert knowledge of:

  • C and/or C++
  • Java
  • JNI API
  • AIX OS
  • AIX C/C++ compilers

The power and flexibility that come with JNI come at the cost of portability. The WORA principle certainly does not apply when JNI is involved. JNI specifications are defined by Sun Microsystems Inc. Only JNI specification 1.0 native methods were specific to Sun's JVM. JNI specifications starting with 1.1 define JVM-neutral native method interface, variable access, and method invocation JNI API. However, compiler variants and options to create shared libraries for use through JNI API are different on different platforms. More importantly, the JNI specification does not dictate how JNI is to be implemented, so JNI implementation varies from one JVM vendor to another. Sun JCK and JNI specification assert conformance to the specification, but not to an implementation. Problems might arise with code that was developed with the assumption of methods implementation, instead of strictly complying with specification.


General JNI programming considerations

This section discusses general JNI programming issues, such as performance, exception handling, and memory management.

JNI performance

Writing portions of the application, especially those that are heavily used, in native code and linking it with Java is usually intended to improve performance. However, communication between JVM and native code is generally slow, and too many JNI calls can degrade performance. Handling exceptions within JNI native code itself can also degrade performance.

JNI exceptions

Handling exceptions natively can be essential when integrating native code with Java. JNI allows native functions to catch exceptions themselves, or to throw exceptions back to JVM. Handling exceptions within native code can degrade performance, but is necessary if JNI function returns error instead of success status. Handling exceptions within JNI code itself usually requires usage of the following JNI functions: ExceptionOccurred(), IsInstanceOf(), ExceptionCheck(), and ExceptionClear() to clear the exception. These functions are computationally expensive, ExceptionCheck() being less expensive than ExceptionOccurred(), as the latter has to create an object to be referred to as well as a local reference.

When an exception has been thrown, no further JNI function calls should be made after the exception, except those calls that deal with exception, until flag has been cleared. Further calls may lead to unpredictable results, including JVM abnormal termination. Programmers should check for any exceptions that can be fixed natively, clear the exception, then fix it. If exception is not checked, the next JNI code invocation will detect pending exception and throw it. Debugging exceptions thrown from a different point in the code than the point where the exception was actually created will definitely be more complicated.

Native and Java memory

Using native code increases the chance of memory leaks. JVM garbage collector (GC) no longer shields programmers from the necessity of deallocating used native resources. Some JNI interface functions help in releasing previously allocated resources: ReleaseStringUTFChars(), ReleaseStringChars(), DeleteGlobalRef(), DeleteLocalRef(), and so on.

Local references
References from native code to Java objects on the Java heap might not be in a form traceable by GC. JNI automatically creates a local reference to any object that is referenced from native code on the Java heap, which is essentially a pointer to an address on the Java heap created in the respective thread stack. Local references are valid for the duration of a native function call. They are freed automatically when out of scope after the native function returns, but passed to all the functions on the stack. The local reference is passed to all the functions called within the function that created local reference originally.

With local references there are two common issues to consider:

Losing local reference
When the method in which local reference was created exits, that local reference goes out of scope. An object may still exist on the Java heap, but a pointer to it - from the native function stack local reference area that GC can see - is lost. This object is eligible for collection as unreachable, as far as GC is concerned. GC cannot trace any native references from any native thread stack that may exist. At this point, use of native references that might exist on the native stack can be fatal, as nothing guarantees existence of the object on the Java heap during the next GC cycle.
Local reference capacity
Each local reference costs some amount of JVM resource. Although local references are automatically freed after the native method returns to Java, excessive allocation of local references may cause JVM to run out of memory during the execution of a native method. Function EnsureLocalCapacity (JNIEnv *env, jint capacity) ensures that at least a given number of local references can be created in the current thread. It returns 0 on success; otherwise, it returns a negative number and throws an OutOfMemoryError. Before it enters a native method, JVM automatically ensures that at least 16 local references can be created as dictated by JNI specification. For backward compatibility, JVM allocates local references beyond the ensured capacity. For debugging support, JVM may give a warning that too many local references are being created: ***ALERT: JNI local ref creation exceeded capacity

In the Java 2 SDK, programmers can supply the -verbose:jni command line option to turn on these messages. The JNI specification does not dictate local reference capacity of a JVM, nor does it require use of this message. This message might or might not appear, and at different times, in JVMs from different vendors.

Copied versus pinned data
Converting arrays from Java to native code is complex because, as opposed to native languages, elements in Java arrays are not always placed together in contiguous memory. When a native function requires access to an array created in Java, JNI must organize the array correctly, then must ensure that the array is returned back to JVM when native function completes.

There is a substantial difference between creating object arrays and primitive type arrays through JNI. Function NewObjectArray() takes as arguments array size (jint), the array type (jclass), and initial value for each array element (usually NULL). Creating arrays of primitives is similar, though New<primitive_type>Array() takes only one argument, which is the size of the array. Each of these functions builds a Java array. However, the elements of that array may not be contiguous in memory. To obtain access to individual elements of a primitive array within native code requires a call to Get<primitive_type>ArrayElements, with the j<primitive_type>Array and jboolean as arguments. The jboolean is passed by reference, and is set by the function to either JNI_TRUE or JNI_FALSE. If the array is laid out in contiguous memory, the resulting jboolean is JNI_FALSE, meaning that native code has a direct access pointer to the array, and that data is pinned. Otherwise, JNI_TRUE means that a copy of the array was created, and it can be moved in a GC compaction cycle or collected.

Whether jboolean is JNI_TRUE or JNI_FALSE or not, the contract that the JNI specification makes is that a reference returned by Get<primitive_type>ArrayElements is valid until the corresponding Release<type>ArrayElements() is executed. JNI specification states "It is not possible to predict whether any given JVM will copy or pin data on any particular JNI call." If jboolean is JNI_FALSE, the array will be pinned, preventing JVM from garbage collecting it or moving it during the GC compaction phase. The JNI specification states clearly that one must Release<primitive_type>ArrayElements() when one has finished using an array reference. It is not conditional on the reference being a copy; it does not matter if the jboolean variable is JNI_TRUE or JNI_FALSE, Release<primitive_type>ArrayElements() must be called to avoid memory leak, and update the array in JVM.

IBM JVM generally uses the pinning implementation. A common programming error is to free only copied data, so that the heap gradually becomes more and more fragmented and filled with chunks of pinned data until eventually failure occurs. This is another example of problems that might arise if an application was written relying on a particular JVM implementation instead of strictly adhering to the JNI specification. Freeing only copied data would probably work correctly on a JVM that prefers copy method. If the application is migrated to a pinning JVM or a JVM changes its implementation, the code will fail if not written to the specification.

As a general rule, Release<type> () should always be called after any function that uses a jboolean flag that shows whether returned data is a copy or pinned.

Release<primitive_type>ArrayElements() must be used, requiring the arguments: JNIEnv interface pointer, the array itself, a pointer to the array, and the mode in which it is to be released. If the data was pinned, any changes made to it were copied directly into the Java heap, so mode parameter is ignored. If the jboolean flag indicates return data as a copy, the mode flag should be used to make changes made to the data. Generally, to avoid implementation-specific details of JVMs from different vendors, the most generic way is to pass NULL (0) for jboolean flag and always pass 0 for the mode flag.

Unlike an array of primitive types, there is no handle to the object array in JVM in a format that C/C++ can use. There is no way to convert a JVM object into a C++ object; native code must access it in JVM memory space through JNI. There is no need to release JVM's object arrays since they are never copied from the JVM's memory space, so JVM's GC will take care of that.


AIX JNI programming specifics

This section covers JNI on AIX samples, API versions, shared libraries, multithreading, and AIX compilers.

JNI on AIX samples

Programmers testing their skills with JNI API on AIX for the first time are strongly encouraged to install the sample fileset for AIX JDK, and become familiar with JNI code examples and information. For AIX JDK 1.4.1 filesets are Java14.samples for 32 bit version, and Java14_64.samples for 64-bit version. It is also recommended you read the JDK User Guide, especially the sections pertinent to JNI compatibility.

JNI API versions

In JDK 1.4.1 it is no longer possible to use the JNI 1.1 interface. You can't call JNI_CreateJavaVM() and pass it a version of JNI_VERSION_1_1(0x00010001). The versions that can be passed are JNI_VERSION_1_2(0x00010002) and JNI_VERSION_1_4(0x00010004).

JNI shared libraries

The most common programming errors when creating and using JNI shared libraries are:

  • Not including both libjava.a (../jre/bin) and libjvm.a (../jre/bin/classic) in the LIBPATH for programs that embed and start JVM through JNI Invocation API.
  • Setting SETUID on the binary executable that starts JVM through JNI Invocation API.
  • Not including, or not having correct permissions on, directories holding JNI shared objects.
  • Trying to load 32-bit shared objects into a 64-bit process, or vice versa.
  • Not creating a thread-safe reentrant shared object for multi-threaded applications.

The environment variable LIBPATH tells AIX applications, such as the JVM, where to find shared libraries. This use of LIBPATH is equivalent to the use of LD_LIBRARY_PATH in other Unix-based systems. The shared libraries for the JVM are in the ./jre/bin and ./jre/bin/classic subdirectories of the Java installation. For compatibility with scripts designed for other Unix-based systems, the Java launcher programs prepend LD_LIBRARY_PATH, if it is set, to the beginning of LIBPATH. However, if your application needs to search specific directories when looking for shared libraries, the preferred variable to set is LIBPATH. If the application starts JVM from its code using Invocation API, you must include both of the above-mentioned directories to load both libjava.a and libjvm.a.

If the program binary executable has SETUID bit on, the LIBPATH variable is automatically cleared when the program starts due to security reasons. For JNI Invocation API application that loads libraries dynamically (using dlopen()) can prevent calls that need LIBPATH to search for shared objects, such as the JNI_CreateJVM() call, from working since the LIBPATH is no longer set up properly. Either the SETUID bit needs to be removed, or the application needs to be run as root user. When SETUID permission bit needs to be preserved, another alternative that might work, depending on the actual environment would be to set LIBPATH explicitly in the code with the setenv() statement. For example,

setenv("LIBPATH","/usr/java14/jre/bin:/usr/java14/jre/bin/classic", 1);

When the JNI application throws java.lang.UnsatifiedLinkError, it usually indicates a problem with the way the application was written, compiled, or configured. Most common programming errors are: not having a correct library path for System.loadLibrary() to search, permissions on directories in the path, or calling native methods before loading the library. Generally, the library should be loaded in a static block, ensuring that the library is loaded before any native methods are called.

Trying to load a 64-bit shared library into a 32-bit JVM process address space, or vice versa, will also most likely result in java.lang.UnsatisfiedLinkError. There is no way to load a 64-bit object into a 32-bit process space, or vice versa. The linker appropriately selects objects from the library  based on the type of linking that is requested (32-bit or 64-bit), and creates an object or application of that type.

JNI shared libraries to be loaded into JVM must be created as reentrant thread-safe objects, with appropriate compiler variants, as discussed in more detail in AIX compilers.

JNI and multithreading on AIX

The IBM JVM for AIX uses the AIX POSIX pthreads package for threading. Java threads created by the JVM use the POSIX pthreads model supported on AIX. Currently, this is on a 1-to-1 mapping with the kernel threads. When developing a JNI program, you must run with a 1-to-1-thread model and system contention scope if creating pthreads in your own program. This can be controlled using the following environment setting:

export AIXTHREAD_SCOPE=S

Another option is to preset in the code the thread's scope attribute to PTHREAD_SCOPE_SYSTEM using the AIX pthread_attr_setscope() function when the thread is created.

By default, the AIXTHREAD_MUTEX_DEBUG, AIXTHREAD_RWLOCK_DEBUG and AIXTHREAD_COND_DEBUG environment variables are set to OFF. These three variables disable the lists of mutexes, read-write locks, and condition variables that are used by the debugger, thus reducing the overhead of maintaining the lists. They need to be set to OFF for JNI Invocation API programs.

If your JNI application is multithreaded, special attention must be paid to POSIX specifications implemented on AIX. Some POSIX specifications are left undefined and therefore platform-specific. Native code implementing multithreading might need to be changed when porting JNI code from one OS to another. The threads library provides some advanced features to be used by trained programmers. Implementation of the advanced features and attributes is optional, and depends on POSIX options. It might require special attention when porting the application from a non-AIX platform to AIX, or between different AIX releases.

For example, you might need to set pthread stack size. The pthread stacksize attribute is defined in AIX. It depends on the stack size POSIX option, which might not be implemented on other systems. The stacksize attribute specifies the minimum stack size that will be allocated for a thread. The pthread_attr_getstacksize() subroutine returns the value of the attribute, and the pthread_attr_setstacksize() subroutine sets the value. If the value of stacksize is less than 96KB, a stack of 96KB is to be allocated by default in current AIX implementation of the threads library. The allocation is always a multiple of 4KB.

The stack size of the main thread is controlled by the user-based ulimit parameter. The stack size of the JVM-created threads, such signal handler thread, finalizer thread, and so on, is controlled by the native thread stack size -Xss parameter. It defaults to:

  • 256K for JDK 122 and 130
  • 512K for JDK 131 and JDK 1.4.1 32-bit
  • 1M for JDK 1.4.1 64-bit

The values are subject to change. If a thread is created without specifying the stack size with pthread_attr_setstacksize(), thread stack size defaults to AIX base value. Joining the thread created without specifying the stack size (created with JNI_CreateJavaVM() through AttachCurrentThread()) can result in stack overflow and SIGSEGV. Small default AIX pthread stack size might not be enough for computational complexity requirements usually presented to JVM threads. See the following code excerpt that summarizes issues mentioned in this section:

pthread_attr_t attr; pthread_t tid;
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); /* sets system 
   contention scope for thread */
pthread_attr_setstacksize(&attr, 512*1024); /* sets stack size to 512K */
   default for 32-bit JVM 1.4.1 and 1.3.1 threads */
pthread_create(&tid, &attr, launchThread, NULL);
pthread_join(tid,&status);

AIX compilers

It is very important to have the latest fixes applied to the IBM C/C++ compiler you use, and the latest fixes for the C++ RTE (xlC.* filesets) installed. Base levels and updates for the xlC environment are available at the time of this writing from ftp://aix.software.ibm.com/aix/products/ccpp/.

You must use thread-safe reentrant compiler variants of VAC and VACPP, such as xlC_r or xlc_r/cc_r, to compile C++ or C code, respectively, into thread-safe reentrant shared objects that are to be linked into a shared library. Use ld for C code, and makeC++SharedLib_r for C++ code, for later loading into JVM process space through JNI. Alternatively, you would need to manually pass to non-reentrant compiler variant macros and add libraries that the above-mentioned reentrant thread-safe compiler variants use by default.


Glossary

AIX – Advanced Interactive Executive Operating System

API – Application Programming Interface

GC – Garbage Collector

JCK – Sun Java Compatibility Kit

JDK – Java Development Kit

JNI – Java Native Interface

JRE – Java Runtime Environment

JVM – Java Virtual Machine

OS – Operating System

RTE – Run Time Environment

VAC – IBM Visual Age C Compiler

VACPP – IBM Visual Age C++ Compiler

WORA – Write Once Run Anywhere


Resources

About the author

Nikolay Yevik, a Linux on POWER consultant in IBM’s Solutions Enablement group, has more than 5 years of experience working on UNIX platforms, performing development work in C, C++ and Java. He has Masters degrees in Petroleum Engineering and Computer Science.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=AIX and UNIX
ArticleID=13070
ArticleTitle=JNI Programming on AIX
publish-date=03152004
author1-email=yevik@us.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers