5

JDK-16 interfaces for JVM connection

 2 years ago
source link: https://devm.io/java/jdk16-jvm-api
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java Foreign Memory API and Foreign Linker API

JDK-16 interfaces for JVM connection


Since the existence of Java, there’s been a need to access libraries and foreign memory written with other programming languages. This is especially true for those developed with C/C++. For this, the Java platform offered only the Java Native Interface (JNI) - until now.

Using JNI, applications can include native code written in programming languages such as C/C ++ and Java. From the perspective of the Java platform, the JNI API enables the inclusion of legacy code and the resolution of interoperability issues between Java code and natively compiled code (Fig. 1). Memory operations aren’t without problems, since native code is executed, used objects have to be freed again, and the Java application is directly returned to between native calls without being able to detect potential errors in the native code.

Project Panama

To better assist developers to accomplish this programming task, Project Panama [1] was created. It serves to facilitate interaction between JVM and native code. The connections between the JVM and the well-defined non-Java interfaces (Foreign APIs) were revised for this purpose - in particular the APIs commonly used by C programmers. Project Panama contains the JDK Enhancement Proposals for the Foreign Memory Access API (JEP-383) [2], the Foreign Linker API, (JEP-389) [3] and the Vector API, (JEP-338) and the following components:

  • native function call of the JVM
  • native data access of the JVM or within the JVM heap
  • new data layouts in the JVM heap
  • native metadata definition for the JVM
  • API extraction tools for header files (jextract)
  • native library management APIs
  • native oriented interpreter and runtime hooks
  • class and method resolution hooks
  • natively oriented JIT optimizations
  • security compliance tools or wrappers
  • workarounds for hard-to-integrate native libraries

Foreign Memory Access API

With the Foreign Memory Access API in its third incubation edition, JEP 393 is part of JDK 16 [4]. The API enables Java programs to securely access external memory outside the Java heap. A clear separation of roles between the _MemorySegment _and _MemoryAddress _interfaces has been created. The new interface _MemoryAccess _contains general static memory access functions and minimizes the use of the VarHandle API in simple cases. Shared memory segments are supported and the segments offer the possibility to register them with a cleaner.

In general, the interface should be able to interoperate with different types of foreign memory, such as native memory, persistent memory, and managed heap memory. It should be avoided that the API undermines JVM security, regardless of the type of memory being worked with. The clients should have control options, for example, memory segments should be released again - either explicitly via a method call or implicitly if the segment is no longer used. For application programs that need to access foreign memory, the API is intended to be a viable alternative to older Java APIs such as sun.misc.Unsafe. However, a reimplementation of obsolete Java APIs like sun.misc.Unsafe is not intended.

alt_text

Fig. 1: Java application call from native C/C++ code via JNI

The previous approach with JNI

Since early JDK versions such as Java 1.1, there has been a standardized approach for connecting native libraries in the form of the Java Native Interface (JNI). In contrast to the progressive innovation of the JDK from version to version (Coin, Lambda, Java Module System), working with the JNI has not evolved in terms of software technology and requires the programmer to have knowledge of C and C++ and associated tools. The high entrance hurdles and the reduction of the error degree with the JNI use led to the conception of the Java Foreign Linker API.

Foreign Linker API

Java includes a runtime library that provides a wide range of necessary functionality for software development. If the case arises that functionality is not implemented by the JRE, then it can be supplied either by the binding of Java libraries or by means of native libraries. In the following, we will consider the binding of native functionality. Depending upon the platform this is either with Windows in DLLs, with Linux, or macOs in shared libraries.

With JEP 389, JDK 16 initially receives the Foreign Linker API under the Incubating project status. Access to native libraries can now be statically typed (in JNI everything is a_ jobject*_) using pure Java Glue code generated by the Native API. The Foreign Memory API (https://openjdk.java.net/jeps/383) functionality drastically reduces entry barriers for developers to use native libraries safely and without errors. Additional structural improvements were not made in JDK 16 for this purpose. The following design goals show the differences to JNI on the concept level:

  • Ease of use: JNI is replaced by a more powerful, intuitive development approach.
  • Priority C support: The initial focus for the introduction of the Foreign Linker API serves the use of libraries compiled from C code, therefore the platforms x64 and AArch64 are served first.
  • Long-term commonality: The Foreign Linker API and its implementation should have enough flexibility in the long term to serve libraries of other platforms (e.g. 32-bit x86) and imported functions of languages other than C (e.g. C++ or Fortran).
  • Performance and efficiency: The Foreign Linker API is expected to provide processing times comparable to or better than JNI.

The development tool jextract is not included in JDK 16, but is shipped with the Project Panama Early Access builds (OpenJDK version 17-panama) [5], in MS Windows in the path D:\jdk-17\bin\jextract.exe. The jextract tool, which strictly speaking, is not part of the runtime-related Foreign Linker API, simplifies development by generating Java classes from the C header files (Fig. 2). When running jextract, the incubator modules jdk.incubator.foreign and jdk.incubator.jextract are used. The module and package names are the same.

alt_text

Fig. 2: Java application call from native C/C++ code with Foreign Linker API.

Loading libraries and control flow

To load libraries, before using a native library, it is necessary to load it for the first time and analyze its structure. In classic JNI, the System::loadLibrary and System::load methods are used as a facade for dlopen. The Foreign Linker API provides the LibraryLookup class for this purpose. It can be used to access symbols of a loaded library, specifically with the lookup method, which returns an object of type LibraryLookup.Symbol if successful:

LibraryLookup libclang = LibraryLookup.ofLibrary("somelibrary");
LibraryLookup.Symbol clangVersion = libclang.lookup("someMethod");

Another difference to JNI is the binding of ClassLoader to the native library, i.e. a loaded JNI library can only be used from within the visibility range of a ClassLoader. This restriction does not exist with the Foreign Linker API, due to the fact that Java objects are never passed to the native libraries, the same library can also be used by several ClassLoader instances in parallel.

To include native libraries in the control flow of Java programs, there are two different types of call. One is the traditional downcall, i.e. C code is called from Java. This will usually be a function listed in the header file. The second call method is the upcall. For example, if a callback method written in Java is to be used (compare) when using native qsort for comparison purposes, the function pointer to compare is encapsulated in a MemorySegment and passed to the qsort method as part of the function call. With the availability of up- and downcall, Java code and native code can interactively work together.

Calculation example of a SHA-256 checksum with OpenSSL

The calculation of a SHA-256 checksum using OpenSSL (Listing 1) serves as an illustrative example of the use of the Foreign Linker API. Instead of the Malloc and Free brackets common in C code, the Foreign Memory API handles memory allocations. For performance reasons, an object of the type NativeScope is first created using the try-with-resources pattern, i.e., allocations in this visibility scope are served from the non-Java heap. First, a memory area of length 216 bytes (resulting from the summed field lengths of the SHA256_CTX structure, 8x8+2x8+16x8+2x4) is reserved.

Listing 1: Calculating SHA-256 with Foreign Linker API

import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.NativeScope;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import static openssl.sha.sha_h.*;
import static jdk.incubator.foreign.CLinker.*;
public class ShaTest {


  public static void main(String[] args) throws Exception {


  String val = args[0];
  byte[] orig = val.getBytes(); 


  System.out.println("\n[+] Calculating SHA-256 of  '"+val+"' "+Arrays.toString(orig)+" with OpenSSL");


  try (var scope = NativeScope.unboundedScope()) {
  /*
  typedef struct SHA256state_st {
  SHA_LONG h[8];
  SHA_LONG Nl, Nh;
  SHA_LONG data[SHA_LBLOCK];
  unsigned int num, md_len;
  } SHA256_CTX; */ 
    int ictx256len = 8*8+2*8+16*8+2*4;
    var pSha256ctx = scope.allocate(ictx256len);
    var data = scope.allocate(1024);
    var databb = data.asByteBuffer(); 
    databb.put(orig);
    var md=scope.allocate(32); //32
    int rc = SHA256_Init(pSha256ctx);
    // OpenSSL returns 1 in case of Success, this took a while to notice 
    if (rc!=1) {
      System.exit(-1);
    }
    rc = SHA256_Update(pSha256ctx,data,orig.length);
    if (rc!=1) {
      System.exit(-1);
    }
    rc = SHA256_Final(md,pSha256ctx);
    if (rc!=1) {
      System.exit(-1);
    }
    //System.out.println("b");


    byte[] arr = new byte[32]; 
    md.asByteBuffer().get(arr,0,32);
    System.out.println(Arrays.toString(arr));
    System.out.println(bytesToHex(arr));
    }
    System.out.println("\n[+] Calculating SHA-256 of  '"+val+"' "+Arrays.toString(orig)+" with java.security");
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    byte[] encodedhash = digest.digest(orig);
    System.out.println(Arrays.toString(encodedhash));
    System.out.println(bytesToHex(encodedhash));
  }


  private static String bytesToHex(byte[] hash) {
    StringBuilder hexString = new StringBuilder(2 * hash.length);
    for (int i = 0; i < hash.length; i++) {
      String hex = Integer.toHexString(0xff & hash[i]);
      if(hex.length() == 1) {
        hexString.append('0');
      }
      hexString.append(hex);
    }
    return hexString.toString();
  }}

This is done with the allocate command, which takes the length in bytes as an int parameter, and returns an object of type MemorySegment.

A data buffer (as a fixed value of 1024 for simplicity) is reserved as the second allocation. This is then encapsulated in an object of the MemorySegment type. Via the method asByteBuffer this memory segment can then be used as ByteBuffer in Java. The string value to be hashed is written to this byte buffer via a put. In the third memory area, 32 bytes are reserved to store the result of the SHA256 operation. Now that all the required memory areas are available, the SHA256 methods provided by OpenSSL can be executed:

  • SHA256_Init
  • SHA256_Update
  • SHA256_Final

The result of the operation is available in the first parameter referenced memory area after SHA256_Final is completed. That is the area reserved in advance for the checksum of length 32 bytes. These bytes are also transferred to the Java context by means of a byte buffer and can be used for integrity checks in the same way as the Java method MessageDigest::digest.

Foreign Linker API in the build process

To use the Foreign Linker API in Java programs, the following steps are necessary:

  • Calling jextract to create Java access classes and interfaces for C header files.
  • Using the classes of the java.foreign APIs to provide the corresponding runtime scenario
  • Calling the methods corresponding to the original C functions previously created with jextract

The SHA256 example requires the following command line calls:

  • The following functions of libcrypto.so are exported (T)
nm -D libcrypto.so | grep SHA256
00000000001cf320 T SHA256
00000000001cf040 T SHA256_Final
00000000001cedd0 T SHA256_Init
00000000001cf030 T SHA256_Transform
00000000001cee30 T SHA256_Update

  • The corresponding headers are located in /usr/include/openssl/sha.h
jextract /usr/include/openssl/sha.h -t openssl.sha  –lcrypto

  • The source code can be found in ShaTest.java, as well as the import of the standard module for the Foreign Linker API
javac --add-modules jdk.incubator.foreign ShaTest.java

  • The main class to run is ShaTest.class and libcrypto.so ,located in /usr/lib/x86_64-linux-gnu/, so you need to enable the native memory access option
java --add-modules jdk.incubator.foreign  -
Djava.library.path=/usr/lib/x86_64-linux-gnu/  -
Dforeign.restricted=permit  ShaTest $1

Conclusion and Outlook

The long-term planned implementation of the Panama project (Foreign Function / Data Interface) with the Foreign Memory Access API and the Foreign Linker API in JDK 16 is taking concrete shape. These interfaces will enable and greatly simplify type-safe access to native libraries. In addition, there is the convenient use of the familiar abstraction classes, such as bytebuffer. Compared to JNI, development times are shortened due to the reduction of access barriers. The use of native additional functions potentially leads to more security than before. The new APIs are on a higher level and offer better possibilities to integrate an external library into Java development projects. This makes it much easier for developers to integrate a machine learning library into an existing Java project, for example with the Tribuo Java Library or the TensorFlow Library (C/C++, Python). Nevertheless, after the incubator status, the Foreign Memory Access API and the Foreign Linker API need the necessary maturity and stability to replace the JNI calls in the long term and to be able to use any third-party code.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK