rodrigo-speller / JNet

A Java Runtime to .NET
Apache License 2.0
5 stars 0 forks source link

Multithreading #19

Open Snuffy4 opened 1 year ago

Snuffy4 commented 1 year ago

Hi, i have tried to use it in a few Threads, but it crashed. Do you have any solutions?

rodrigo-speller commented 1 year ago

Can you post any sample code?

Snuffy4 commented 1 year ago

Can you post any sample code?

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using JNet.Runtime;
using System.Threading;

namespace testJVMBridge {
    public class Program {
        public static JNetVirtualMachine vm = null;
        public static void Main(string[] args) {
            vm = JNetVirtualMachine.Initialize(new() {
                JavaRuntimePath = "/Library/Java/JavaVirtualMachines/jdk-18.0.2.1.jdk/Contents/Home/",
                Classpath = new List<string>() { "testfile.jar" },
            });

            int thread_count = 10;

            for (int i = 0; i < thread_count; i++) {
                Thread myThread = new Thread(Print);
                myThread.Start();
            }

        }

        static void Print() {
            var rt = vm.AttachCurrentThread();
            var mainClass = rt.FindClass("com/testClass");
            var initMethod = rt.GetMethodID(mainClass, "<init>", "()V");
            var initObj = rt.NewObject(mainClass, initMethod);

            var initMainMethod = rt.GetMethodID(mainClass, "mainFunc", "()V");
            rt.CallVoidMethod(initObj, initMainMethod);
            vm.DetachCurrentThread();

        }
    }
}

For example, if i specify 1 thread, it takes about ten seconds. But if i specify 10 threads, it takes more than 30 seconds.

rodrigo-speller commented 1 year ago

Managing the JNetRuntime instances by AttachCurrentThread and DettachCurrentThread can be complicated in your project. To attach and detach multiple threads, you should implement a host to handle the runtime operations to avoid race concerns.

JNet.Hosting is a library that implements a host to manage the JNetRuntime instances. It is a wrapper library around the JNet.Runtime library. I recommend you to use JNet.Hosting to orchestrate the java calls to handle rece conditions.

using JNet.Hosting;

static class Program
{
    static void Main(string[] args)
    {
        var debug = true;

        JNetHost.Initialize(new() {
            EnableDiagnostics = debug
        });

        JNetHost.Run(runtime => {

            // The "runtime" parameter is a JNetRutime object, that exposes the JNI function for usage.
            // See more: https://docs.oracle.com/en/java/javase/15/docs/specs/jni/functions.html

            // java.lang.System class
            var clz_System = runtime.FindClass("java/lang/System");
            // System.out field
            var f_out = runtime.GetStaticFieldID(clz_System, "out", "Ljava/io/PrintStream;");

            // java.io.PrintStream class
            var clz_PrintStream = runtime.FindClass("java/io/PrintStream");
            // PrintStream.println method
            var m_println_A = runtime.GetMethodID(clz_PrintStream, "println", "(Ljava/lang/String;)V");   

            // gets the System.out value
            var j_out = runtime.GetStaticObjectField(clz_System, f_out);

            // creates the "Hello, World!" string
            var j_str = runtime.NewString("Hello, World!");

            // call: System.out.println("Hello, World!")
            runtime.CallVoidMethod(j_out, m_println_A, j_str);

        });

        JNetHost.Destroy();
    }
}

Calling JNetHost.Run is thread safe, so it can be called inside your threads.

Checkout the sample code to understand how JNetHost class can be initialized and used handle these operations.

Snuffy4 commented 1 year ago

And how can I run this in two threads?

rodrigo-speller commented 1 year ago

Calling JNetHost.Run is thread safe, so it can be called inside your threads.

Snuffy4 commented 1 year ago

I need to run threads as needed without deleting the Java VM, but for some reason after the thread finishes, they do not free up RAM

Snuffy4 commented 1 year ago

I also trying your example using

using JNet.Hosting;
using JNet.Runtime;

static class Program {
    static void Main(string[] args) {
        var debug = true;

        JNetHost.Initialize(new() {
            EnableDiagnostics = debug,
            JavaRuntimePath = "/Library/Java/JavaVirtualMachines/jdk-18.0.2.1.jdk/Contents/Home/",
        });

        JNetHost.Run(runtime => {

            // The "runtime" parameter is a JNetRutime object, that exposes the JNI function for usage.
            // See more: https://docs.oracle.com/en/java/javase/15/docs/specs/jni/functions.html

            // java.lang.System class
            var clz_System = runtime.FindClass("java/lang/System");
            // System.out field
            var f_out = runtime.GetStaticFieldID(clz_System, "out", "Ljava/io/PrintStream;");

            // java.io.PrintStream class
            var clz_PrintStream = runtime.FindClass("java/io/PrintStream");
            // PrintStream.println method
            var m_println_A = runtime.GetMethodID(clz_PrintStream, "println", "(Ljava/lang/String;)V");

            // gets the System.out value
            var j_out = runtime.GetStaticObjectField(clz_System, f_out);

            // creates the "Hello, World!" string
            var j_str = runtime.NewString("Hello, World!");

            // call: System.out.println("Hello, World!")
            runtime.CallVoidMethod(j_out, m_println_A, j_str);

        });

        Console.ReadLine();
        //JNetHost.Destroy();
    }
}

https://user-images.githubusercontent.com/44814286/231010411-34fdddae-b0e1-40d2-bd10-b5433c354548.png

During execution, about 25 MB is allocated, but after the execution of the thread, during the standby mode, the memory is not released...

rodrigo-speller commented 1 year ago

I believe that this memory is the JNI libraries overhead. I simulate 100 threads and the memory comsumtion was very stable.

I will investigate this when I have more time.

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using JNet.Hosting;
using JNet.Runtime;

static class Program
{
    static void Main(string[] args)
    {
        PrintMemoryUsage("Program starts");

        var debug = true;

        PrintMemoryUsage("Before JNetHost.Initialize");

        JNetHost.Initialize(new() {
            EnableDiagnostics = debug
        });

        PrintMemoryUsage("Before JNetHost.Run");

        var threads = Enumerable.Range(0, 100).Select(x => {
            var thread = new Thread(() => {
                JNetHost.Run(runtime => {
                    PrintMemoryUsage("Inside JNetHost.Run (starts)");

                    // The "runtime" parameter is a JNetRutime object, that exposes the JNI function for usage.
                    // See more: https://docs.oracle.com/en/java/javase/15/docs/specs/jni/functions.html

                    // java.lang.System class
                    var clz_System = runtime.FindClass("java/lang/System");
                    // System.out field
                    var f_out = runtime.GetStaticFieldID(clz_System, "out", "Ljava/io/PrintStream;");

                    // java.io.PrintStream class
                    var clz_PrintStream = runtime.FindClass("java/io/PrintStream");
                    // PrintStream.println method
                    var m_println_A = runtime.GetMethodID(clz_PrintStream, "println", "(Ljava/lang/String;)V");   

                    // gets the System.out value
                    var j_out = runtime.GetStaticObjectField(clz_System, f_out);

                    // creates the "Hello, World!" string
                    var j_str = runtime.NewString("Hello, World!");

                    // call: System.out.println("Hello, World!")
                    runtime.CallVoidMethod(j_out, m_println_A, j_str);
                    runtime.ThrowExceptionOccurred();
                    PrintMemoryUsage("Inside JNetHost.Run (completes)");
                });

            });

            thread.Start();

            return thread;
        }).ToArray();

        foreach (var thread in threads) {
            thread.Join();
        }

        PrintMemoryUsage("Before JNetHost.Destroy");
        JNetHost.Destroy();

        PrintMemoryUsage("Program completes");
    }

    static void PrintMemoryUsage(string when) {
        Process currentProcess = Process.GetCurrentProcess();
        long mProcess = currentProcess.WorkingSet64;
        var mGc = GC.GetTotalMemory(false);

        Console.WriteLine($"Memory: {when,32}: {mProcess / (1024f * 1024f),6:0.00} {mGc / (1024f * 1024f),6:0.00}");
    }
}

Thank you for your analisys.

Snuffy4 commented 1 year ago

Ok, I want to use this to run a heavy function via JNI in multiple threads. One thread consumes about 200 MB, so after some time the program runs out of RAM and the kernel terminates the program. I will be very grateful when you manage to study the problem. Thanks!

rodrigo-speller commented 1 year ago

@Snuffy4, this problem, that the kernel terminates the program, is happening before or after you use JNetHost.Run?

I'm questioning this, because I found (and fix) a bug that terminates the program on JNetHost.Run.

22