Since I don't have a Mac M, I can only provide my approach to compiling ARM on x86_64:
For my case, I compile programs on an x86_64 system targeting the ARM platform.
For your case, you should be compiling on your ARM environment to target the x86_64 (amd64) platform.
Project Directory
create a base directory: Java-JNA-Go-MultiArch
The following commands, unless otherwise stated, are executed in the Java-JNA-Go-MultiArch directory by default.
Verify that Docker's QEMU is supported.
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
Test ARM container
Then test whether the ARM container can be executed:
docker run --rm --platform linux/arm64 alpine uname -m
If aarch64 is output, it means QEMU has started normally.
Test AMD container (x86_64)
docker run --rm --platform linux/amd64 alpine uname -m
output: x86_64, it means QEMU has started normally.
go_mylib
crate go_mylib directory.
Java-JNA-Go-MultiArch
└── go_mylib
├── mylib.go
├── linux-arm-64 (dir)
└── linux-x86-64 (dir)
mylib.go
package main
import "C"
//export add_integers
func add_integers(a C.int, b C.int) C.int {
return a + b
}
//export concat_strings
func concat_strings(x *C.char, y *C.char) *C.char {
result := C.CString(C.GoString(x) + C.GoString(y))
return result
}
func main() {}
Build
Build ARM64
docker run --rm --platform linux/arm64 \
-v $(pwd)/go_mylib:/app -w /app \
golang:latest \
go build -o linux-arm-64/libmylib.so -buildmode=c-shared mylib.go
Build AMD 64
docker run --rm --platform linux/amd64 \
-v $(pwd)/go_mylib:/app -w /app \
golang:latest \
go build -o linux-x86-64/libmylib.so -buildmode=c-shared mylib.go
Final Result
Java-JNA-Go-MultiArch
└── go_mylib
├── mylib.go
├── linux-arm-64
│ ├── libmylib.h
│ └── libmylib.so
└── linux-x86-64
├── libmylib.h
└── libmylib.so
Important
When compiling into a shared library (.so) using the Go language, it will reference the version of GLIBC, so you must know which GLIBC version is being used.
Run command:
docker run -it --rm --platform linux/arm64 \
golang:latest \
bash
and
docker run -it --rm --platform linux/amd64 \
golang:latest \
bash
In container:
ldd --version
get result: GLIBC 2.36
ldd (Debian GLIBC 2.36-9+deb12u9) 2.36
java_app_call_mylib
Project Driectory
Java-JNA-Go-MultiArch
└── java_app_call_mylib
├── pom.xml
└── src
└── main
└── java
└── com
└── example
└── JnaExample.java
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>go-jna-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- JNA dependency for native library access -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.11.0</version>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
</build>
</project>
JnaExample.java
package com.example;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public class JnaExample {
// Define interface mapping to local library
public interface MyLib extends Library {
//Load dynamic library
MyLib INSTANCE = Native.load("mylib", MyLib.class);
int add_integers(int a, int b);
Pointer concat_strings(String x, String y);
}
public static void main(String[] args) {
// Call the add_integers function
int sum = MyLib.INSTANCE.add_integers(10, 20);
System.out.println("Sum: " + sum);
//Call concat_strings function
Pointer resultPointer = MyLib.INSTANCE.concat_strings("Hello, ", "World!");
String resultString = resultPointer.getString(0); //Read string from pointer
System.out.println("Concatenated String: " + resultString);
// Manually release the memory allocated by malloc
Native.free(Pointer.nativeValue(resultPointer));
}
}
Build
Build in docker container
run command in Java-JNA-Go-MultiArch
docker run --rm \
-v $(pwd)/java_app_call_mylib:/app -w /app \
maven:3.8.2-eclipse-temurin-17 \
mvn clean package
docker run --rm \
-v $(pwd)/java_app_call_mylib:/app -w /app \
maven:3.8.2-eclipse-temurin-17 \
mvn dependency:copy-dependencies -DoutputDirectory=target/libs
or
you can run command in Java-JNA-Go-MultiArch/java_app_call_mylib
mvn clean package
mvn dependency:copy-dependencies -DoutputDirectory=target/libs
Output Result
Java-JNA-Go-MultiArch
└── java_app_call_mylib
...
└── target
├── app.jar
└── libs
└── jna-5.11.0.jar
app
We create an app directory and test it based on the above results.
create app under Java-JNA-Go-MultiArch
Java-JNA-Go-MultiArch
└── app
├── app.jar
├── libs
│ └── jna-5.11.0.jar
├── linux-arm-64
│ └── libmylib.so
└── linux-x86-64
└── libmylib.so
- app.jar : copy it from java_app_call_mylib/target
- libs/jna-5.11.0.jar : copy it from java_app_call_mylib/target
- linux-arm-64/libmylib.so : copy it from go_mylib
- linux-x86-64/libmylib.so : copy it from go_mylib
Test ARM64
in Java-JNA-Go-MultiArch
Run command:
docker run -it --rm --platform linux/arm64 \
-v $(pwd)/app:/app -w /app \
openjdk:24-ea-17-jdk-bookworm \
bash
in container:
export LD_LIBRARY_PATH=`pwd`/linux-arm-64
java --enable-native-access=ALL-UNNAMED -cp "libs/*:app.jar" com.example.JnaExample
Test AMD64 (For your needs)
in Java-JNA-Go-MultiArch
Run command:
docker run -it --rm --platform linux/amd64 \
-v $(pwd)/app:/app -w /app \
openjdk:24-ea-17-jdk-bookworm \
bash
in container:
export LD_LIBRARY_PATH=`pwd`/linux-x86-64
java --enable-native-access=ALL-UNNAMED -cp "libs/*:app.jar" com.example.JnaExample
output:
Sum: 30
Concatenated String: Hello, World!
in container:
ldd --version
output:
ldd (Debian GLIBC 2.36-9+deb12u8) 2.36
Why, it keeps checking the GLIBC version?
Run openjdk:17
docker run -it --rm --platform linux/amd64 \
-v $(pwd)/app:/app -w /app \
openjdk:17 \
bash
in container:
# ldd --version
ldd (GNU libc) 2.28
export LD_LIBRARY_PATH=`pwd`/linux-x86-64
java --enable-native-access=ALL-UNNAMED -cp "libs/*:app.jar" com.example.JnaExample
output error:
Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'mylib':
/lib64/libc.so.6: version `GLIBC_2.34' not found (required by /app/linux-x86-64/libmylib.so)
Native library (linux-x86-64/libmylib.so) not found in resource path (libs/jna-5.11.0.jar:app.jar)
at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:301)
...
...
Suppressed: java.lang.UnsatisfiedLinkError: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by /app/linux-x86-64/libmylib.so)
That is to say, if your x86_64 target machine has an older GLIBC version, the same error may occur when JDK calls libxxx.so generated by GO.