pyjnius
pyjnius copied to clipboard
Passing PythonJavaClass object to Java method/class does not increase Python ref counter
MWE:
import sys
from jnius import autoclass, PythonJavaClass, java_method
class OneSupplier( PythonJavaClass ):
__javainterfaces__ = [ 'java.util.function.IntSupplier' ]
def __init__( self, *args, **kwargs ):
super( OneSupplier, self ).__init__()
@java_method('()I')
def getAsInt(self):
return 1
ArrayList = autoclass('java.util.ArrayList')
al = ArrayList()
os = OneSupplier()
l = []
print("Ref count before", sys.getrefcount(os))
al.add(os)
print("Ref count after", sys.getrefcount(os))
l.append(os)
print("Ref count after", sys.getrefcount(os))
Example output:
Ref count before 7
Ref count after 7
Ref count after 8
I would expect the ref count in the second row to increase but it does not. This is counter-intuitive and problematic when Python objects can go out of scope in the Python code but stay alive in Java (in some other Thread, for example).
I think this issue leads to a set of strange issues where complex java objects get garbage collected on the Python side, when you leave e.g. a function scope. When the object, which is garbage collected is added to another Java Object by reference (e.g. put in a hashmap) the reference to this object will be lost, even though you might have returned the parent object (e.g. the hashmap) from the function.
Does anyone has an idea, how to fix this. This goes way over my head.
For reference here is the same code in JPype.
import sys
import jpype
from jpype.types import *
from jpype import JImplements, JOverride
jpype.startJVM()
@JImplements('java.util.function.IntSupplier')
class OneSupplier:
@JOverride
def getAsInt(self):
return 1
ArrayList = JClass('java.util.ArrayList')
al = ArrayList()
os = OneSupplier()
l = []
print("Ref count before", sys.getrefcount(os))
al.add(os)
print("Ref count after", sys.getrefcount(os))
l.append(os)
print("Ref count after", sys.getrefcount(os))
With output
Ref count before 2
Ref count after 3
Ref count after 4
The code that is doing the work is the reference queue. Everytime a proxied Python object is used, it checks to see if there is a Java instance for the object. If it is found it can be reused. Otherwise, it creates a new reference and places it in the reference queue which will destroy the Python reference when Java removes it. This can also by used to create "map" type proxies in which Java can hold onto any arbitrary Python object as a generic Object. Though this feature needs to be used carefully as if you create a Python object which holds references to Java objects and then tell it to wrap as a Java object you have created an unbreakable referencing loop causing leaks.