Some developers used JNA to call native C/C++ interfaces of Dynamsoft Barcode Reader in Java program. The app ran slowly and sometimes crashed. Based on the use case, I created a simple project for building JNA and JNI on Windows. The sample does not only show how to invoke native code but also share how to check the Java thread stack size and solve the stack overflow issue on Windows.
Download and Installation
Accessing Native Code via JNA and JNI
Open Eclipse to create a new project. The structures of JNA and JNI folders are almost the same.
Image may be NSFW.
Clik here to view.
Java code
Create the Java function decodefile() for JNA and JNI:
Image may be NSFW.
Clik here to view.
C/C++ code
For JNA, you just need to create a standard function and export it. For JNI, you have to comply with the JNI naming convention.
Image may be NSFW.
Clik here to view.
Building shared libraries with CMake
Copy shared libraries to <Project Root>/jna(jni)/platforms/win:
- DBRx64.lib
- DBRx86.lib
- DynamsoftBarcodeReaderx64.dll
- DynamsoftBarcodeReaderx86.dll
Create CMakeLists.txt:
cmake_minimum_required (VERSION 2.6) project (dbr) MESSAGE( STATUS "PROJECT_NAME: " ${PROJECT_NAME} ) # Check platforms if (CMAKE_HOST_WIN32) set(WINDOWS 1) set(JAVA_HOME "E:/Program Files (x86)/Java/jdk1.8.0_191") set(JAVA_INCLUDE "${JAVA_HOME}/include") set(JAVA_INCLUDE_OS "${JAVA_HOME}/include/win32") endif() MESSAGE( STATUS "JAVA_INCLUDE: " ${JAVA_INCLUDE}) # Configure a header file to pass some of the CMake settings # to the source code configure_file ( "${PROJECT_SOURCE_DIR}/BarcodeReaderConfig.h.in" "${PROJECT_BINARY_DIR}/BarcodeReaderConfig.h" ) # Add search path for include and lib files if(WINDOWS) link_directories("${PROJECT_SOURCE_DIR}/platforms/win" "${JAVA_HOME}/lib") include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_SOURCE_DIR}" "${JAVA_INCLUDE}" "${JAVA_INCLUDE_OS}") endif() # Add the executable add_library(jnadbr SHARED NativeBarcodeReader.cxx) if(WINDOWS) if(CMAKE_CL_64) target_link_libraries (jnadbr "DBRx64") else() target_link_libraries (jnadbr "DBRx86") endif() endif() # Set installation directory set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/../") if(WINDOWS) install (DIRECTORY "${PROJECT_SOURCE_DIR}/platforms/win/" DESTINATION "${CMAKE_INSTALL_PREFIX}" FILES_MATCHING PATTERN "*.dll") install (TARGETS jnadbr DESTINATION "${CMAKE_INSTALL_PREFIX}") endif()
To build the shared library for JNI, replace ‘jnadbr’ with ‘jnidbr’.
Build the project for 64-bit Java runtime:
cd jna mkdir build cd build cmake -G"Visual Studio 15 2017 Win64" .. cmake --build . --config Release --target install
Create Demo.java for test:
public static void main(String[] args) { int count = 1; JNABarcode jnaReader = new JNABarcode(); System.out.println("JNA start."); long start = System.currentTimeMillis(); for (int i = 0; i < count; i++) { jnaReader.decodefile(); } long end = System.currentTimeMillis(); System.out.println("JNA end. Time cost: " + (end - start) + "ms"); JNIBarcode jniReader = new JNIBarcode(); System.out.println("JNI start."); start = System.currentTimeMillis(); for (int i = 0; i < count; i++) { jniReader.decodefile(); } end = System.currentTimeMillis(); System.out.println("JNI end. Time cost: " + (end - start) + "ms"); }
Run the app:
javac -cp libs\jna-3.0.9.jar src\com\dynamsoft\*.java java -cp libs\jna-3.0.9.jar;src\ com.dynamsoft.Demo
Image may be NSFW.
Clik here to view.
Why is running JNA much slower than running JNI?
The native code is the same, why the performance is different? The only possible reason is when calling the native method the first time it takes a long time to initialize the JNA environment.
Image may be NSFW.
Clik here to view.
We can call the native method to initialize the JNA environment before measuring the performance.
Image may be NSFW.
Clik here to view.
Build and rerun the app. The JNA performance of invoking native method looks no different to JNI.
Image may be NSFW.
Clik here to view.
Now we get the conclusion that using JNA takes extra loading time.
Java VM Stack Overflow Exception
The stack overflow exception showed up when runtime environment switched from 64-bit to 32-bit.
Image may be NSFW.
Clik here to view.
According to https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/crashes001.html, the issue is caused by thread stack size. To solve the issue, we’d better know the default thread stack size. Let’s try the following command:
Linux
java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
Image may be NSFW.
Clik here to view.
Windows
java -XX:+PrintFlagsFinal -version | findstr ThreadStackSize
Image may be NSFW.
Clik here to view.
The value of stack size is 0 on Windows. Maybe the answer is in the source code.
Download and install TortoiseHg.
Get JVM source code:
hg clone http://hg.openjdk.java.net/jdk8/jdk8/hotspot/
Search for ‘stack size’ to find the following informative paragraph.
Image may be NSFW.
Clik here to view.
To verify whether the default stack size is 320K on Windows, I tried the code snippet located in os_windows.cpp:
typedef u_char* address; #include <windows.h> address current_stack_base() { MEMORY_BASIC_INFORMATION minfo; address stack_bottom; size_t stack_size; VirtualQuery(&minfo, &minfo, sizeof(minfo)); stack_bottom = (address)minfo.AllocationBase; stack_size = minfo.RegionSize; while( 1 ) { VirtualQuery(stack_bottom+stack_size, &minfo, sizeof(minfo)); if ( stack_bottom == (address)minfo.AllocationBase ) stack_size += minfo.RegionSize; else break; } #ifdef _M_IA64 #endif return stack_bottom + stack_size; } size_t sz; MEMORY_BASIC_INFORMATION minfo; VirtualQuery(&minfo, &minfo, sizeof(minfo)); sz = (size_t)current_stack_base() - (size_t)minfo.AllocationBase; printf("The default Java thread stack size for this system is %u bytes(%u KB).\n", sz, sz / 1024);
To avoid stack overflow exception, we should allocate big chunks of memory on the heap instead of the stack.
Image may be NSFW.
Clik here to view.
Re-compile the native code in x86 mode:
cd jna mkdir build cd build cmake .. cmake --build . --config Release --target install
Compile and run the Java code with 32-bit JDK:
"E:\Program Files (x86)\Java\jdk1.8.0_191\bin\javac.exe" -cp libs\jna-3.0.9.jar src\com\dynamsoft\*.java "E:\Program Files (x86)\Java\jdk1.8.0_191\bin\java.exe" -cp libs\jna-3.0.9.jar;src\ com.dynamsoft.Demo
Image may be NSFW.
Clik here to view.
We can see the default stack size is 320K by default on Windows. To enlarge the stack size, append -Xss option when running Java program:
"E:\Program Files (x86)\Java\jdk1.8.0_191\bin\java.exe" -Xss1024 -cp libs\jna-3.0.9.jar;src\ com.dynamsoft.Demo
References
- https://docs.microsoft.com/en-us/windows/desktop/api/Winnt/ns-winnt-_memory_basic_information
- https://msdn.microsoft.com/en-us/library/windows/desktop/aa366902(v=vs.85).aspx
- https://docs.microsoft.com/en-us/windows/desktop/procthread/thread-stack-size
- https://msdn.microsoft.com/en-us/library/kdzttdcb.aspx?f=255&MSPPError=-2147217396
- https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/crashes001.html
- https://docs.microsoft.com/en-us/windows/desktop/api/Winnt/ns-winnt-_memory_basic_information
Source Code
https://github.com/yushulx/jna-jni-windows
The post Java Programming: JNA vs. JNI on Windows appeared first on Code Pool.