1

Is there a good way, native through JNI or not, to do shared-memory in Java using shmget? I'm currently doing shared-memory through a memory mapped file, but I want to do it in a non-file way through shmget.

More info here: https://unix.stackexchange.com/questions/747838/is-it-possible-for-two-processes-to-use-the-same-shared-memory-without-resorting

  • OP mentioned using JNI to call shmget. Your comment does not apply. And chatgpt usage is banned in Stackoverlow:https://meta.stackoverflow.com/questions/421831/temporary-policy-chatgpt-is-banned. Use your own content instead. – aled Jun 04 '23 at 03:16
  • Pure way in what context? For Java most people would interpret that it means pure a Java solution without JNI. You should explain what problem are you trying to actually solve instead of skipping directly to a possible solution. – aled Jun 04 '23 at 03:21
  • @aled I edited. Changed `pure way` to `non-file way`. Thanks! – ThreadFrank Jun 04 '23 at 03:30
  • What are you trying to achieve and why are Java memory mapped files are not adequate. Note that memory mapped is faster than normal files and managed by the OS. – aled Jun 04 '23 at 03:57
  • 1
    @aled My friend, it is well explained. You are looking for hair on an eggshell. Please let other people take a shot instead of keep being negative. Of course I know what mmap files are. This is not my question. Please stop stating the obvious to mess with my question. Memory-mapped files have to be swapped in and out of disk. You understand there is a cost there, right? Do you think `shmget` exists just to annoy you? Anyway, I'm not answering any more of your comments. Peace! – ThreadFrank Jun 04 '23 at 04:07
  • 1
    You should be able to set this up with [Foreign Memory API and jextract](https://jdk.java.net/jextract/), various versions are available from JDK16 upwards but not that this incubating or preview status. – DuncG Jun 04 '23 at 07:37
  • Why? What practical difference have you identified that would make `shmget()` better than a memory-mapped file? – user207421 Jun 04 '23 at 10:09
  • @user207421 There is no _SWAPPING_ involved with `shmget()` as data does not have to be persisted to the memory-mapped file. `shmget()` exists to allow you to use shared-memory without the persistency penalty. Check here: https://unix.stackexchange.com/questions/747838/is-it-possible-for-two-processes-to-use-the-same-shared-memory-without-resorting – ThreadFrank Jun 04 '23 at 10:54
  • @ThreadFrank understanding the context of the question is important. Consider it as a part of the "what has been tried". You may have excellent reasons but they need to be shared with readers. Questions should be as self-contained as possible. You should put all the information in the question, not with links to other questions or outside sites. – aled Jun 04 '23 at 12:24
  • Thanks for schooling man. You are the best and one of the reasons why SO is such a friendly place! – ThreadFrank Jun 04 '23 at 13:00

1 Answers1

2

As DuncG suggested, you can use java.lang.foreign to access C functions. However, there are some caveats:

  • As of Java 20, this is still a preview feature, which means public classes and their members may change, or may be removed entirely, in the next few Java releases.
  • Compiling requires the compiler options --enable-preview --release 20. I also recommend the -Xlint:-preview option.
  • Running requires the Java command line options --enable-preview --enable-native-access=com.example. Replace com.example with your module name, or replace it with ALL-UNNAMED if you are not declaring a module.
import java.nio.ByteBuffer;

import java.io.IOException;

import java.lang.foreign.Linker;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.Arena;

import java.lang.invoke.MethodHandle;

import java.util.Objects;

import java.util.function.Consumer;

public class SHM {
    private static final boolean IS_64_BIT =
        System.getProperty("os.arch").contains("64");

    private static MemorySegment lookupFunction(String name) {
        return Linker.nativeLinker().defaultLookup().find(name).orElseThrow(
            () -> new RuntimeException("Function " + name + "() not found."));
    }

    private static MemorySegment lookupValue(String name) {
        return Linker.nativeLinker().defaultLookup().find(name).orElseThrow(
            () -> new RuntimeException("Value " + name + " not found."));
    }

    private static final MethodHandle strerror =
        Linker.nativeLinker().downcallHandle(
            lookupFunction("strerror"),
            FunctionDescriptor.of(
                ValueLayout.ADDRESS,    // return
                ValueLayout.JAVA_INT)   // errnum
        );

    private static final MethodHandle shmget =
        Linker.nativeLinker().downcallHandle(
            lookupFunction("shmget"),
            FunctionDescriptor.of(
                ValueLayout.JAVA_INT,       // return
                ValueLayout.JAVA_INT,       // key
                IS_64_BIT                   // size
                    ? ValueLayout.JAVA_LONG
                    : ValueLayout.JAVA_INT,
                ValueLayout.JAVA_INT)       // shmflg
        );

    private static final MethodHandle shmat =
        Linker.nativeLinker().downcallHandle(
            lookupFunction("shmat"),
            FunctionDescriptor.of(
                ValueLayout.ADDRESS,        // return
                ValueLayout.JAVA_INT,       // shmid
                ValueLayout.JAVA_INT)       // shmflg
        );

    private static final MethodHandle shmdt =
        Linker.nativeLinker().downcallHandle(
            lookupFunction("shmdt"),
            FunctionDescriptor.of(
                ValueLayout.JAVA_INT,       // return
                ValueLayout.ADDRESS)        // shmaddr
        );

    private static String strerror(int errnum)
    throws IOException {
        MemorySegment message;
        try {
            message = (MemorySegment) strerror.invokeExact(errnum);
        } catch (Throwable t) {
            throw new IOException(t);
        }

        String messageStr;
        try (Arena arena = Arena.openConfined()) {
            // java.lang.foreign always returns memory addresses
            // as zero-length MemorySegments.
            MemorySegment bounded = MemorySegment.ofAddress(message.address(),
                4096, arena.scope());
            messageStr = bounded.getUtf8String(0);
        }

        return messageStr;
    }

    private static int errno()
    throws IOException {
        MemorySegment seg = lookupValue("errno");

        int num;
        try (Arena arena = Arena.openConfined()) {
            // java.lang.foreign always returns memory addresses
            // as zero-length MemorySegments.
            MemorySegment bounded = MemorySegment.ofAddress(seg.address(),
                Integer.BYTES, arena.scope());
            num = bounded.get(ValueLayout.JAVA_INT, 0);
        }

        return num;
    }

    public void useSharedMemory(int shmKey,
                                int shmSize,
                                Consumer<ByteBuffer> task)
    throws IOException {

        if (shmSize < 0) {
            throw new IllegalArgumentException("Size cannot negative.");
        }
        Objects.requireNonNull(task, "Task cannot be null.");

        try (Arena arena = Arena.openConfined()) {
            int shmid;
            try {
                if (IS_64_BIT) {
                    shmid = (int) shmget.invokeExact(shmKey, (long) shmSize, 0);
                } else {
                    shmid = (int) shmget.invokeExact(shmKey, shmSize, 0);
                }
            } catch (Throwable t) {
                throw new IOException(t);
            }

            if (shmid == -1) {
                throw new IOException(
                    "shmget() returned " + shmid + ": " + strerror(errno()));
            }

            MemorySegment addr;
            try {
                addr = (MemorySegment) shmat.invokeExact(
                    shmid, MemorySegment.NULL, 0);
            } catch (Throwable t) {
                throw new IOException(t);
            }

            if (addr.address() == -1) {
                throw new IOException(
                    "shmat() returned " + addr.address() + ": "
                    + strerror(errno()));
            }

            // java.lang.foreign always returns memory addresses
            // as zero-length MemorySegments.
            MemorySegment boundedAddr =
                MemorySegment.ofAddress(addr.address(), shmSize, arena.scope());

            task.accept(boundedAddr.asByteBuffer());

            int status;
            try {
                status = (int) shmdt.invokeExact(addr);
            } catch (Throwable t) {
                throw new IOException(t);
            }

            if (status != 0) {
                throw new IOException(
                    "shmdt() returned " + status + ": " + strerror(errno()));
            }
        }
    }
}

Fair warning: I have only done minimal testing on the above code.

VGR
  • 40,506
  • 4
  • 48
  • 63
  • Thanks @VGR. I think at this point for a production grade system it is best to go with JNI. I was hoping that someone somewhere had already written a wrapper project for that. But I've searched everywhere and was not able to find it. – ThreadFrank Jun 04 '23 at 18:52
  • For what it’s worth, [this is the intended replacement for JNI.](https://openjdk.org/jeps/412) – VGR Jun 04 '23 at 19:09