Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multithreading #19

Open
Snuffy4 opened this issue Apr 5, 2023 · 10 comments
Open

Multithreading #19

Snuffy4 opened this issue Apr 5, 2023 · 10 comments

Comments

@Snuffy4
Copy link

Snuffy4 commented Apr 5, 2023

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

@rodrigo-speller
Copy link
Owner

Can you post any sample code?

@Snuffy4
Copy link
Author

Snuffy4 commented Apr 7, 2023

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
Copy link
Owner

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
Copy link
Author

Snuffy4 commented Apr 9, 2023

And how can I run this in two threads?

@rodrigo-speller
Copy link
Owner

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

@Snuffy4
Copy link
Author

Snuffy4 commented Apr 10, 2023

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
Copy link
Author

Snuffy4 commented Apr 10, 2023

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
Copy link
Owner

rodrigo-speller commented Apr 11, 2023

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
Copy link
Author

Snuffy4 commented Apr 11, 2023

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
Copy link
Owner

rodrigo-speller commented Apr 16, 2023

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants