79726452

Date: 2025-08-05 17:28:13
Score: 0.5
Natty:
Report link

The FFM APIs mentioned by @LouisWasserman are not stable yet. But I did more research and found that the VarHandle API lets us perform atomic store/loads/ops with any memory order of our choice on any Java value: fields, array elements, bytebuffer elements and more.

Note: it's extremely hard to test the correctness of concurrent code, I'm not 100% sure that my answer is memory-safe.

For the sake of simplicity, I'll focus on a release-acquire scenario, but I don't see any reason why atomic_fetch_add wouldn't work. My idea is to share a ByteBuffer between C and Java, since they're made specifically for that. Then you can write all the data you want in the ByteBuffer, and in my specific case about Java-to-C transfer, you can do an atomic release-store to make sure that all data written prior to the atomic store will be visible to anyone acquire-loading the changed "ready" flag. For some reason, using a byte for the flag rather than an int throws an UnsupportedOperationException. The C code can treat the ByteBuffer's backing memory as whatever it wants (such as volatile fields in a struct) and load them using usual atomic functions.

I'm assuming that a good JVM should easily be able to optimise hot ByteBuffer read/stores into simple instructions (not involving method calls), so this approach should definitely be faster than doing JNI calls on AtomicIntegers from the C side. As a final note, atomics are hard to do right, and you should definitely use them only if the performance gain is measurable.

Appendix — sample Java and C code

I don't think StackOverflow supports collapsible sections, sorry for the visual noise.

This example uses a memory map to have shared memory between Java and C, but JNI should work just as well. If using JNI, you should use env->GetDirectBufferAddress to obtain the void* address of a direct ByteBuffer instance's internal buffer.

How to use: Run the Java program first. When it tells you to, run the C program. Go back to the Java console, enter some text and press enter. The C code will print it and exit.

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Scanner;

public class Main {
    private static final int MMAP_SIZE = 256;
    private static final VarHandle BYTE_BUFFER_INT_HANDLE = MethodHandles.byteBufferViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN);

    public static void main(String[] args) throws IOException {
        try (var mmapFile = FileChannel.open(Path.of("mmap"), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING)) {
            assert mmapFile.write(ByteBuffer.wrap(new byte[0]), MMAP_SIZE) == MMAP_SIZE;
            var bb = mmapFile.map(FileChannel.MapMode.READ_WRITE, 0, MMAP_SIZE);

            // Fill the byte buffer with zeros
            for (int i = 0; i < MMAP_SIZE; i++) {
                bb.put((byte) 0);
            }
            bb.force();

            System.out.println("You can start the C program now");
            // Write the user-inputted string after the first int (which corresponds to the "ready" flag)
            System.out.print("> ");
            String input = new Scanner(System.in).nextLine();
            bb.position(4);
            bb.put(StandardCharsets.UTF_8.encode(input));

            // When the text has been written to the buffer, release the text by setting the "ready" flag to 1
            BYTE_BUFFER_INT_HANDLE.setRelease(bb, 0, 1);
        }
    }
}
#include <sys/mman.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdatomic.h>

#define MMAP_SIZE 256
#define PAYLOAD_MAX_SIZE (MMAP_SIZE - 4)

typedef struct {
  volatile int32_t ready;
  char payload[PAYLOAD_MAX_SIZE];
} shared_memory;

int main() {
  int mapFile = open("mmap", O_RDONLY);
  if (mapFile == -1) {
    perror("Error opening mmap file, the Java program should be running right now");
    return 1;
  }

  shared_memory* map = (shared_memory*) mmap(NULL, MMAP_SIZE, PROT_READ, MAP_SHARED, mapFile, 0);
  if (map == MAP_FAILED) {
    perror("mmap failed");
    close(mapFile);
    return 1;
  }

  int ready;
  while (!(ready = atomic_load_explicit(&map->ready, memory_order_acquire))) {
    sleep(1);
  }

  printf("Received: %.*s", PAYLOAD_MAX_SIZE, map->payload);
}
Reasons:
  • Blacklisted phrase (1): StackOverflow
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: anon