Skip to main content

Compilation of JNI Code

Posted by kellyohair on January 9, 2006 at 7:23 PM PST

I've added a few clarifying comments in italics below.

Using the JavaTM Native Interface (JNI) is not something many Java programmers have to deal with, but when you do, you need to know something about native applications.
Whether it's Windows, linux, or Solaris,
each native platform and sometimes native compiler or even
the release of the native compiler
has slightly different issues, so I thought I would write a little on each of these issues.

With JNI you are actually building a native shared library (.so or .dll file) that the
JavaTM Virtual Machine (JVM) will dynamically load into the Java native process with dlopen() on Solaris and Linux, or LoadLibrary() on Windows.
This "dynamically loaded shared library" fact is the basis for many of the compilation issues, not so much the use of JNI, dynamic loading of native libraries can be tricky.
Some of the compilation issues relate to the library being loaded into a Java process and causing conflicts in the way the JDK was compiled itself, or the way the JDK assumes code was compiled (sometimes this is considered a JDK bug, but it depends on a great number of factors).

All libraries loaded into java are assumed to be MT-safe
(Multi-thread safe).
This means that multiple threads could be executing the
code at the same time,
and static or global data may need to be placed in critical sections.
If you don't know what MT-safe means, you should do some research
and make sure you understand what this means, it is a critical concept
to understand when creating JNI libraries and writing JNI code.
So if there is a compiler option to select MT-safe generated code, you will need it.

Compiler optimizations are always an issue and you need to be careful
with high optimization levels.
The safest route is to stick with just -g compilations, then
use lowest optimization level you need, increasing it only
when your native code and the overall application really
benefits from increased optimizations.

Tips

So here a a few tips on the different platforms and the common
compiler options to watch out for:

    Solaris

  • On Solaris, using the Sun Studio 11 C compiler,
    the typical compile and link command lines might look something like:



    For 32bit SPARC:



      cc -xO2 -v -mt -xc99=%none -xCC -Xa -xregs=no%appl -xmemalign=4s -xstrconst -xarch=v8 -KPIC -DTRIMMED -DNDEBUG -Dsparc -D__solaris__ -D_REENTRANT  -DTRACING -DMACRO_MEMSYS_OPS -DBREAKPTS -c *.c




    cc -mt -xarch=v8 -Mmapfile -z defs -ztext -G -o libXXX.so *.o -lc




For 64bit SPARC:



    cc -xO2 -v -mt -xc99=%none -xCC -Xa -xregs=no%appl -xstrconst -xarch=v9 -KPIC -DTRIMMED -DNDEBUG -Dsparc -D__solaris__ -D_REENTRANT  -DTRACING -DMACRO_MEMSYS_OPS -DBREAKPTS -c *.c




    cc -mt -xarch=v9 -Mmapfile -z defs -ztext -G -o libXXX.so *.o -lc




For X86:



    cc -xO2 -mt -v -xc99=%none -xCC -Xa -xregs=no%frameptr -xstrconst -KPIC -DTRIMMED -DNDEBUG -Di586 -D__solaris__ -D_REENTRANT -DcpuIntel -D_LITTLE_ENDIAN= -Di386 -DTRACING -DMACRO_MEMSYS_OPS -DBREAKPTS -c *.c




    cc -mt -Mmapfile -z defs -ztext -G -o libXXX.so *.o -lc


For AMD64:



    cc -xO2 -mt -v -xc99=%none -xCC -Xa -xregs=no%frameptr -xstrconst -xarch=amd64 -KPIC -DTRIMMED -DNDEBUG -Di586 -D__solaris__ -D_REENTRANT -DcpuIntel -D_LITTLE_ENDIAN= -Damd64 -DTRACING -DMACRO_MEMSYS_OPS -DBREAKPTS -c *.c




    cc -mt -xarch=amd64 -Mmapfile -z defs -ztext -G -o libXXX.so *.o -lc


  • Architecture/File Format:
    For SPARC 32bit use -xarch=v8,
    for SPARC 64bit use -xarch=v9,
    for X86 (32-bit)

    leave the option off or use -xarch=generic
    ,
    and for AMD64 (64bit) use -xarch=amd64
    with both C and C++.



    This is to be specific as to the architecture and the file format
    of the .o files (and ultimately of the .so).
    Other -xarch values may work, but many won't mix well with the native
    code of the JDK, so be careful.
    Also be careful when using -xarch and some of the other compiler optimization options that target specific processors
    (e.g. -fast should not be used unless you follow it with an overriding -xarch option).
  • MT-Safe, Position Independent: Use -KPIC -mt
    with both C and C++
    .


    The -mt should be used for all compiles and links,
    it makes sure the library is
    built properly to be a MT-safe library.
    The -KPIC option makes sure your code is built in a
    position-independent way to maximum use in a shared library.
  • Register usage: For SPARC (both 32bit and 64bit) use
    -xregs=no%appl and for X86 and AMD64 use -xregs=no%frameptr
    with both C and C++
    .


    On SPARC, certain global registers should not be used by system libraries
    and on X86, you will be better off if the frame pointer register
    is NOT used as a general purpose register.
    Not using -xregs=no%frameptr can cause problems
    in getting accurate stack traces in native code.
  • Alignment: For SPARC 64bit do NOT use -xmemalign=4
    with both C and C++
    .


    Usually the defaults for memory alignment is acceptable, but the
    general theory is that a shared library compiled with great alignment
    is probably ok, with less alignment is a SIGBUS
    disaster waiting to happen.
    We build the SPARC 32bit JDK with
    -xmemalign=4s to be compatible
    with any user libraries built by older compilers that had this as the
    default.
    Newer Sun compilers have a default of
    -xmemalign=8s, so we
    explicitly ask for -xmemalign=4s.
    We build the SPARC 64bit JDK with
    -xmemalign=8s explicitly (which has
    always been the default), if the
    -xmemalign=4s option is used
    to build a SPARC 64bit library, it's likely the JDK will crash
    with a SIGBUS (alignment error).
  • Optimization: The higher the optimization level, the greater the risk
    of running into some kind of mix problem, much of the JDK is built
    with
    -xO4, and the rest with
    -xO2, and sometimes the JVM can make
    assumptions about native code optimizations that can cause problems.


    For example, on X86
    it is important that the code in the native libraries have frame pointer registers, which is why we use
    -xregs=no%frameptr, and prevent this optimization.
    Sometimes optimizations above -xO2 can remove the use of frame pointer registers (wanting the extra general purpose register for
    performance sake)
    and this can cause problems when doing stack frame walking.
    The -xO2 level has proven to be relatively safe.
  • C++ Exceptions: Use -features=no%except -DCC_NOEX
    if your C++ code does not use C++ exceptions.


    Unless you are well aware how to handle C++ exceptions in
    shared libraries.
    Very little of the JDK code uses C++ exceptions.



    The problem, as I understand it, is when the C++ exception escapes
    your shared library, you really don't want this to happen.
    So if you do use C++ exceptions, you just need to
    keep it 'contained' so to speak, inside your own library, handling
    them internally or turning them into Java exceptions with JNI.
    There also can be some code performance loss when the
    C++ compiler is
    generating code that needs to handle C++ exceptions.
    So if your code is not using C++ exceptions at all,
    it's best to tell the compiler that,
    it really can't know at compile time.
  • Quality: Use -Xa -v -xstrconst -xc99=%none -errwarn=%all
    with the C compiler, and -errwarn=%all with the C++ compiler.


    Although not required,
    I have found these options to be helpful in tracking down
    problems in C code.
    The -Xa allows both ISO standard C and K&R C
    but favors ISO Standard C when they conflict.
    The -v asks for more warning messages.
    The -xstrconst forces all string literals in a
    strictly read-only area of memory.
    The -xc99=%none makes sure C99 language features
    are not used (assuming you want highly portable C code).
    The -errwarn=%all option turns all warning errors
    into fatal, and you might not want this, but if you have spent
    some time in getting all your code error free, it might be worthwhile.
  • Library for java_g: The library must be named with a _g.so suffix.


    The Mustang (JDK 6) release does not have a 'java_g' version
    so this applies to Tiger (JDK 5) and older releases only.
  • Library: Use -G -z defs -z text when building your library.


    Although not required, ideally you want your shared library to have
    a read-only and 100% shared text or code segment.
    These options will help guarantee that.
  • Mapfiles: Use -M mapfile.


    Although this isn't technically required, it is highly recommended.
    A mapfile (called a version script in Linux) allows you to
    control various aspects of the shared library that is built.
    The feature of mapfiles that I recommend people
    should use here is the ability to limit
    the extern symbols accessable from your library.
    For example, if you had a native method called "func"
    in Java class "bar" which was in the Java package "foo",
    ultimately with the use of javah you would end up with an
    external symbol name of "Java_foo_bar_func", and your JNI
    shared library would have an extern symbol with that name.
    But there could be lots of extern functions in your native library,
    and odds are, all you want to expose is this one name.
    This mapfile would do the trick:



    Interface_1.1 {
            global:
                Java_foo_bar_func;
            local:
                *;
    };



    Creating a shared library with only the one symbol you wanted
    to make available.

  • Static Linking: Do NOT use static linking of the C++ runtime.


    Static links mean that the code from a library is actually
    copied into your own library, e.g. you inherit extern symbols
    The C++ runtime library is /usr/lib/libCrun.so (and also
    /usr/lib/libCstd.so).
    Although it's possible to do this, it just isn't worth it.
  • C++ Library Runpath and Dependencies: Use -norunpath -xnolib.


    The -xnolib makes it so you are explicit with
    every library you want loaded, so if you indeed want all the
    C++ runtime support libraries, you would need to add
    -lCstd -lCrun -lm -lc.
    The -norunpath has to do with the runpath or runtime
    path directories built into the library you have built.
    In general you do not want to create a library that refers to
    any directories in your C++ compiler installation area.
    The libCrun.so and libCstd.so
    libraries will be (and should be) found in /usr/lib and in
    general you don't need to do anything special for those libraries.
    But if you have other private libraries that you are dependent on
    you need to deal with the runpath issue (see the -R
    option in the man pages).
  • Dependencies: Use ldd -r LibraryName.


    After the shared library has been built, the utility
    ldd can be used to verify that all dependent libraries
    have been satisfied, and all externs can be found.
    If ldd says anything is missing, it is very likely that the JVM will also
    be unable to load this library.
    This usually means that you missed some -lname
    options when building the library, or perhaps forgot a -R path
    option that tells the library where to look for libraries at runtime.


    Also, inspect the list of libraries that your library needs,
    does it make sense?
  • Linux

  • On Linux, using the gcc version 3.2,
    the typical compile and link command lines might look something like:


    For X86:



      gcc -O2 -fno-strict-aliasing -fPIC -pthread -W -Wall -Wno-unused -Wno-parentheses -DNDEBUG -Di586 -DARCH='"i586"' -DLINUX -D_LARGEFILE64_SOURCE -D_GNU_SOURCE -D_REENTRANT -D_LITTLE_ENDIAN -c *.c




      gcc -Wl,-O1 -Wl,-version-script=mapfile -z defs -Wl,-soname=libXXX.so -static-libgcc -shared -mimpure-text -o libXXX.so *.o -lc


    For AMD64:



      gcc -O2 -fno-strict-aliasing -fPIC -pthread -W -Wall -Wno-unused -Wno-parentheses -pipe -DNDEBUG -Damd64 -DARCH='"amd64"' -DLINUX  -D_LARGEFILE64_SOURCE -D_GNU_SOURCE -D_REENTRANT -D_LITTLE_ENDIAN -D_LP64=1 -c *.c




      gcc -Wl,-O1 -Wl,-version-script=mapfile -z defs -Wl,-soname=libXXX.so -static-libgcc -shared -mimpure-text -o libXXX.so *.o -lc


  • MT-Safe, Position Independent:
    Use -fPIC -pthread -D_REENTRANT.


    The -D_REENTRANT should be used for all compiles and links, I don't know all the details on the use of this macro,
    I just know we use it.

    I suspect that the use of -pthread means
    that you also get -D_REENTRANT, but I'm not 100% sure of that
    on all versions of Linux or GNU compilers, so you should probably
    check the compiler you are using.

    In addition we use -Di586 -DARCH='"i586"' -DLINUX -D_LARGEFILE64_SOURCE -D_GNU_SOURCE -D_LITTLE_ENDIAN
    on X86
    and -DAMD64 -DARCH='"AMD64"' -D_LP64=1 on AMD64.
    The -KPIC option makes sure your code is built in a
    position-independent way to maximum use in a shared library.
  • Register Usage: Use -fno-omit-frame-pointer.


    It is important that these libraries have frame pointer register usage, see the above comments on the Solaris
    -xregs=no%frameptr
    option.
  • Pointer aliasing: Use -fno-strict-aliasing.


    Avoid this pointer optimization, on Solaris compilers this optimization
    kicks in at -xO5, and you'd want to avoid it there too.
    If you want this optimization you need to make sure that
    all pointer usage in the native code follows the rules specified
    by this option.
  • Optimizations: Much of the JDK is built
    with
    -O3, and the rest with
    -O2.


    The same general comments on optimizations apply to this
    compiler as to the above Sun compiler.
    Be careful as you increase the optimization level.
    The -O2 level has proven to be relatively safe.
  • Quality: Use -W -Wall -Wno-unused -Wno-parentheses -Werror.


    Although not required,
    I have found these options to be helpful in tracking down
    problems in C code with gcc by issuing more warning errors.
    The -Werror turns all warning errors
    into fatal ones.
  • Library: Use -shared -z defs -Wl,-O1 -Wl,-soname=LibraryName.


    When building the shared library,
    this -soname option makes sure the library name is stored inside the library.

    The -O1 linker option will help reduce the size of
    the resulting shared library.
  • Version scripts (mapfiles): Use -Xlinker -version-script=mapfile.

  • Library for java_g: The library must be named with a _g.so suffix.


    The Mustang (JDK 6) release does not have a 'java_g' version
    so this applies to Tiger (JDK 5) and older releases only.
  • Library: Use -static-libgcc -mimpure-text.


    When building the shared library (-shared option), this option
    allows for maximum portability of the library between different
    flavors of Linux.
    The problem we have seen with Linux is that we cannot depend
    on a compatible shared gcc library existing on all the versions of
    Linux we can run on.
    By doing this static link, the version script becomes more
    important, making sure you don't expose any extern symbols
    you didn't intend to.
  • Dependencies: Use ldd -r LibraryName.


    Provides the same checking as Solaris (see above).
  • Windows

  • On Windows and using the Microsoft C++ Compiler Visual Studio .NET 2003,
    the typical compile and link command lines might look something like:


    For X86:



      cl /O1 /Zi /MD /D _STATIC_CPPLIB /W3 /DNDEBUG /DWIN32 /DIAL /D_LITTLE_ENDIAN /D_X86_ /Dx86 /DWIN32_LEAN_AND_MEAN
      /c *.c




      link /dll /opt:REF /incremental:no /debug /out:XXX.dll *.obj


    For AMD64:



      cl /O1 /Op /Zi /MD /D _STATIC_CPPLIB /Wp64 /W3 /DNDEBUG /DWIN32 /DIAL /D_LITTLE_ENDIAN /D_AMD64_ /Damd64 /DWIN32_LEAN_AND_MEAN /c *.c




      link /dll /opt:REF /incremental:no /debug /out:XXX.dll *.obj


  • Precise floating point: Use /Op with Visual Studio 6 or 2003 (not needed with Visual Studio 2005).


    Avoid optimizations that change floating point precision.
    This may not be a huge issue for most JNI libraries, but if the
    library is doing floating point and returning those values
    back to Java, you may want to consider this option.
  • Optimizations: Much of the JDK is built
    with
    /O2, and the rest with
    /O1.


    The same general comments on optimizations apply to this
    compiler as to the above Sun and Linux GNU compiler.
    Be careful as you increase the optimization level.
    The /O1 level has proven to be relatively safe
    but we have seen some problems with /O2 on
    Windows 2003 AMD64 with the 64bit PlatformSDK compiler
    which appears to be closer to a Visual Studio 2005 compiler
    than the 2003 one. So be careful with /O2.
  • Quality: Use /W3 /WX.


    Although not required,
    I have found these options to be helpful in tracking down
    problems in C and C++ code by issuing more warning errors.
    The /WX turns all warning errors
    into fatal ones.
    On Windows 64bit AMD64 we also add -Wp64.
  • Library: Use /opt:REF /incremental:no when building the dll.


    You don't want an incrementally linked dll it's too big.
  • MS Runtime: Use the /MD option.


    This is the biggest source of problems with Windows DLL's.
    In general the entire native provess on Windows should be using
    a single runtime, and that means that all your various DLL's
    need to be asking for and using the same runtime.
    So if you are building for Mustang use /MD in all
    cases, and for older JDK releases use it too.
    But if you are building a debug version of a library for the older
    JDK's (one for java_g to use), you need to use /MDd.


    Note: Except for 64bit AMD64, even java_g used /MD
    due to some problems with the debug runtime library on AMD64.
  • Library for java_g: The library must be named with a _g suffix.


    The must be built with /MDd
    except on 64bit AMD64 which uses /MD.
    The Mustang (JDK 6) release does not have a 'java_g' version
    and both the regular and fastdebug builds of Mustang (both named
    'java', not 'java_g') use the /MD option.
  • Dependencies: Use VC++ dumpbin /exports and the VC++ "Dependency Walker".


    Provides dependency information similar to ldd.
  • As you can see, there are lots of issues you can run into
    just building the native library properly.
    Most of this is just about "dynamically loaded native library" problems, and less about JNI.
    I'll try and cover some of the JNI specific coding issues
    in another blog.

    Hope this has provided some source of help, and please let me know
    if I can clarify this blog or correct any of my mistakes,
    which I'm sure I have.

    Happy 2006!.

    Related Topics >>