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

JVM crashes when indexing into reference to vector #672

Open
codeinred opened this issue Apr 10, 2023 · 4 comments
Open

JVM crashes when indexing into reference to vector #672

codeinred opened this issue Apr 10, 2023 · 4 comments

Comments

@codeinred
Copy link

Hello,

We've observed an issue wherein a reference to an internal member variable appears to become dangling after the class that contains that member is garbage-collected.

Question

Can references returned by @ByRef-annotated functions become dangling? Is there a way to prevent the reference from becoming dangling, or to instruct javacpp to make a copy of the underlying object?

GetNumbers obj = makeGetNumbers( 1000 );

VecVecR values = obj.nums(); /// If obj is garbage-collected, does this reference become dangling?

Here, VecVecR and GetNumbers are wrapped types. obj.nums() would return a const reference in C++. The java declaration of nums() is annotated with @ByRef. Full definitions are provided below. On the C++ side of things, GetNumbers is held by a unique_ptr to GetNumbersI.

Definitions

Here, GetNumbers is a wrapper for a C++ class, where the nums() function returns a const reference. This reference points to an internal member variable.

Is it possible for an instance of GetNumbers to be garbage-collected while another variable holds onto the object returned by nums()?

We have a class with a method that returns a vector by const reference:

#pragma once
#include <memory>
#include <vector>


namespace example {
    using R       = double;
    using VecR    = std::vector<R>;
    using VecVecR = std::vector<VecR>;

    struct GetNumbersI
    {
        virtual VecVecR const& nums() const = 0;
    };

    using GetNumbersU = std::unique_ptr<GetNumbersI const>;
    GetNumbersU makeGetNumbers( size_t size );
} // namespace example

Source file:

#include "MyClass.h"

namespace example {
    struct GetNumbers : GetNumbersI
    {
        int     a;
        VecVecR values_;
        int     b;

        GetNumbers( VecVecR values )
        : a{}
        , values_( std::move( values ) )
        , b{}
        {
        }
        virtual VecVecR const& nums() const { return values_; }
    };

    GetNumbersU makeGetNumbers( size_t size )
    {
        return std::make_unique<GetNumbers>( VecVecR( size, VecR( size, 0.0 ) ) );
    }

} // namespace example

This produces the following class:

// Targeted by JavaCPP version 1.5.8: DO NOT EDIT THIS FILE

package com.voladynamics.example;

import java.nio.*;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

import static com.voladynamics.example.global.example.*;


    @Name("example::GetNumbersI") @Properties(inherit = com.voladynamics.presets.example.class)
public class GetNumbers extends Pointer {
        static { Loader.load(); }
        /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */
        public GetNumbers(Pointer p) { super(p); }
    
        public native @Const @ByRef VecVecR nums();
    }

Sample code causing crash

Here is roughly the code that causes the issue:

GetNumbers obj = makeGetNumbers( 1000 );

VecVecR values = obj.nums(); 

/// Some expensive computation, that does not directly reference `obj`
/// It appears that `obj` gets garbage-collected during this time period

/// This code will cause the JVM to crash for newer JVM versions (eg, OpenJDK 17 or 19)
double sum = 0.0;
for (int it = 0; it < 100; it++) {
    for (int i = 0; i < values.size(); i++) {
        for (int j = 0; j < values.get(i).size(); j++) {
            sum += values.get(i).get(j);
        }
    }
}

When a line of code that references obj is appended to the above, the crash goes away:

GetNumbers obj = makeGetNumbers( 1000 );

VecVecR values = obj.nums(); 

/// Some expensive computation, that does not directly reference `obj`
/// It appears that `obj` gets garbage-collected during this time period, if it's not referenced anywhere else in the function

/// This code will cause the JVM to crash for newer JVM versions (eg, OpenJDK 17 or 19)
double sum = 0.0;
for (int it = 0; it < 100; it++) {
    for (int i = 0; i < values.size(); i++) {
        for (int j = 0; j < values.get(i).size(); j++) {
            sum += values.get(i).get(j);
        }
    }
}

/// If this line is uncommented, obj won't be garbage-collected
/// System.out.println( obj.nums().get(0).get(0) );
@saudet
Copy link
Member

saudet commented Apr 10, 2023

Anything can happen in C++, sure. To make all this more manageable, we can use PointerScope:
http://bytedeco.org/news/2018/07/17/bytedeco-as-distribution/

@saudet
Copy link
Member

saudet commented Apr 11, 2023

If using PointerScope isn't an option, we need to make sure that GetNumbers doesn't get deallocated, yes. Unless something after that references it in Java, it might get deallocated since the JVM cannot know that something else in C++ is still referencing it. To prevent that from deallocating C++ objects though, another way to work around that is to set to the "org.bytedeco.javacpp.nopointergc" system property to "true", if that's OK for your application.

@codeinred
Copy link
Author

Hi,

Thank you so much for your response! If we enabled nopointergc, would that prevent destructors from being called? This could be problematic for some objects using RAII, and it seems like it would result in most memory being leaked (although please correct me if I'm wrong here)

I will also read into PointerScope.

@saudet
Copy link
Member

saudet commented Apr 11, 2023

There is no guarantee that GC ever calls any destructors at all, period. That's not something we can rely on. For C++ libraries that expect RAII, that's what PointerScope is for.

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

No branches or pull requests

2 participants