pyjnius icon indicating copy to clipboard operation
pyjnius copied to clipboard

Passing PythonJavaClass object to Java method/class does not increase Python ref counter

Open hanslovsky opened this issue 7 years ago • 3 comments

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).

hanslovsky avatar Jun 15 '18 18:06 hanslovsky

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.

AKuederle avatar Sep 14 '18 08:09 AKuederle

Does anyone has an idea, how to fix this. This goes way over my head.

AKuederle avatar Nov 29 '18 09:11 AKuederle

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.

Thrameos avatar Jul 18 '20 21:07 Thrameos