javacpp
javacpp copied to clipboard
JVM crashes when indexing into reference to vector
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) );