Jme-alloc project

By the way, please also add a multi-threaded allocation/deallocation test case. Allocate from one thread and destroy from another thread…

1 Like

Hmm, it’s actually working, i remember i was getting a cache error yesterday.

package com.jme3.alloc;

import java.nio.ByteBuffer;

public final class TestLibrary {

    public static void main(String[] args) {
        final ByteBuffer buffer = NativeBufferAllocator.createDirectByteBuffer(100000);
        System.out.println(buffer);
        System.out.println(buffer.capacity());
        buffer.put((byte) 100);
        System.out.println(buffer.get(0) + "");
    
        System.out.println(buffer);
        System.out.println(buffer.capacity());
        System.out.println(buffer.get(0) + "");
        buffer.put((byte) 12);
        System.out.println(buffer.get(0) + " ");
        System.out.println(buffer.get(1) + " ");
        System.out.println(buffer);
        NativeBufferAllocator.releaseDirectByteBuffer(buffer);
        System.out.println(buffer.get(0) + " ");
        System.out.println(buffer.get(1) + " ");
        System.out.println(buffer);
        buffer.put((byte) 122);
        System.out.println(buffer.get(2) + " ");

        while (true);
    }
}

Output:

└──╼ $java -jar jme3-alloc.jar 
java.nio.DirectByteBuffer[pos=0 lim=100000 cap=100000]
100000
100
java.nio.DirectByteBuffer[pos=1 lim=100000 cap=100000]
100000
100
100 
12 
java.nio.DirectByteBuffer[pos=2 lim=100000 cap=100000]
-112 
0 
java.nio.DirectByteBuffer[pos=2 lim=100000 cap=100000]
122 

EDIT:
I will see what i did yesterday and report back the details soon.

I think we should create an additional jme3-testalloc module where we should layout our examples there ?

EDIT:
The module will add the output jar as a local file dependency.

1 Like

Sounds good, you may still consider adding some unit tests as well.

1 Like

Yeah sure, please open an issue specifying what kinds of testcases and/or techdemos we need to do before using the API.

1 Like

I tested a dirty example for multithreading:

package com.jme3.alloc;

import java.nio.ByteBuffer;
import com.jme3.alloc.util.NativeBufferUtils;

public final class TestLibrary {
    private volatile static ByteBuffer buffer0;
    private static final Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            buffer0 = (ByteBuffer) NativeBufferUtils.clearAlloc(100);
            buffer0.put((byte) 120);
        }
        
    });
    private static final Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            while (buffer0 == null);
            System.out.println(Thread.currentThread().getName());
            System.out.println(buffer0.get(0));
            NativeBufferUtils.destroy(buffer0);
            System.out.println(buffer0.get(0));
        }
        
    });
    public static void main(String[] args) {
        threadA.start();
        threadB.start();
    }
}

Output:

Thread-0
Thread-1
120
-68
1 Like

I tried this as shown above and it works, but I think this is dangerous, since the java.nio.ByteBuffer is not owning this memory anymore, and if this was the case then the object wrapper should be also de-initialized to null after destroying its memory.

1 Like

Of course, a byte buffer should not be usable after destroyed, I think. We should examine ReflectionAllocator and LWJGL 3 allocator and see what is the behavior in their cases.

1 Like

I would suggest one thread continuously create and fill buffers with some data in a loop and queue them, and the other thread picks them from the queue and destroys them, and checks if they are destroyed.

While the app is running watch the app memory usage (e.g. in Task Manager), it must remain stable.

Note that, buffers sizes should be large enough for this test. (e.g 5MB)

1 Like

Finally, i encountered the promised crash; trying to write to a destroyed buffer will fire a jvm monitor crash:

A workaround will be to destroy the java.nio.ByteBuffer reference to avoid these mistakes of writing on another application or jvm system memory.

If we just nullify the reference after destruction, it will just throw a runtime NullPointerException; if we have tried to use the reference in anything after destruction which is better than a vague jvm crash…

1 Like

Those are the current testcases:

Check the logs on the ubuntu test job.

Notice: there is a runtime crash on windows that disables mac from running because i realized i am using the Unix specific file separator on the NativeBinaryLoader utility :sweat_smile:, since i don’t use windows for development anymore, i forgot this point, so i opened another issue to track this mistake…but anyway let’s focus on techdemos in this PR for now.

2 Likes

I tried with ReflectionAllocator and LWJGLBufferAllocator (lwjgl3) to see how they behave in this matter and here is my finding.

With LWJGLBufferAllocator I can read and write to the buffer even after destroying the buffer!

This is the method JME calls for deallocating:

With ReflectionAllocator if I try to read or write to the buffer after destroying the buffer, it crashes the JVM

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f2b98cd430a, pid=17579, tid=0x00007f2b4b1ae700
#
# JRE version: OpenJDK Runtime Environment (8.0_352-b08) (build 1.8.0_352-b08)
# Java VM: OpenJDK 64-Bit Server VM (25.352-b08 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V  [libjvm.so+0xb0630a]  Unsafe_GetNativeInt+0xba
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# jmonkeyengine/hs_err_pid17579.log
Compiled method (nm)    7230  703     n 0       sun.misc.Unsafe::getInt (native)
 total in heap  [0x00007f2b812d1610,0x00007f2b812d1958] = 840
 relocation     [0x00007f2b812d1738,0x00007f2b812d1780] = 72
 main code      [0x00007f2b812d1780,0x00007f2b812d1958] = 472
Compiled method (nm)    7230  703     n 0       sun.misc.Unsafe::getInt (native)
 total in heap  [0x00007f2b812d1610,0x00007f2b812d1958] = 840
 relocation     [0x00007f2b812d1738,0x00007f2b812d1780] = 72
 main code      [0x00007f2b812d1780,0x00007f2b812d1958] = 472
#
# If you would like to submit a bug report, please visit:
#   https://github.com/adoptium/adoptium-support/issues
#

Yeah, I like this idea, or maybe an IllegalAccessException saying that the buffer is destroyed or something.

1 Like

Try allocating an array of buffers (50-100) buffers for example and then destroy them and try to write to their memory.

Some buffers will let you write to their memory because they may be created in this same thread or they are just thread safe, while other will crash even if you try to access their data…

1 Like

I have merged this PR now, if you want to add more examples, then we had better wait until fixing the NativeBinaryLoader issue, then i will wait for your PR.

1 Like

I have fixed the NativeBinaryLoader issues, if you would like to add more examples.

EDIT:
I will add some instructions for local building and testing the examples on your local device.

Here we go: CONTRIBUTING.md

1 Like

Forget about this, all the [jobject] parameters are read-only local references, they cannot control the real java references, i am documenting everything about these crashes in the TestJvmCrashlogs.java.

Is it possible to manipulate it from java side?

From user code just do buffer = null, from the API code, the parameter is also a local reference.

I see, this is not supposed to be used directly by user code anyway, JME is managing deallocation internally I believe.

1 Like

By the way, is the native deallocation method used in your API and lwjgl3 MemoryUtil the same?