Copyright © 2026 Unity Technologies
Description
This is a regression of the class of defect fixed in IAP 4.11.0 ("Fixed AndroidJavaObject not being disposed causing a global reference table overflow in an edge case", previously reported for BillingClientStateListener in 4.9.3). The 5.x Android store rewrite reintroduces it at a new site: BillingClient.QueryProductDetailsParamsProductList disposes the Java list but never the N AndroidJavaObject elements it wraps. Confirmed identical in 5.4.1 and 5.4.2.
In Runtime/Stores/Android/GooglePlay/AAR/Models/BillingClient.cs, QueryProductDetailsParamsProductList builds one AndroidJavaObject per product and returns the Java list. The caller disposes the list, but the wrapped elements are never disposed. Every other method in that file uses using var or an explicit Dispose(); this one is the outlier. Each undisposed wrapper pins a GlobalJavaObjectRef for the process lifetime, and the managed wrappers are small enough to exert almost no GC pressure, so collection never triggers to reclaim them.
Initial setup
Actual behavior
A failed initialisation leaves Purchaser.IsInitialized false, so a subsequent attempt re-fetches the entire catalogue rather than resuming. Any application that retries initialisation on failure therefore leaks roughly two global references per product per attempt. With a catalogue in the low hundreds and a retry every few seconds, the 51,200 reference ceiling is reached in minutes and ART aborts the process:
JNI ERROR (app bug): global reference table overflow (max=51200)
A reference table dump from a production crash attributed 50,850 of 51,198 references, 99.3% of the table, to this path: 25,028 instances of com.android.billingclient.api.QueryProductDetailsParams$Product, each pinning a java.lang.Class.
Symbolicated call chain:
art::JNI::NewGlobalRef
GlobalJavaObjectRef..ctor
AndroidJavaObject..ctor
BillingClient.QueryProductDetailsParamsProduct
WhereSelectListIterator.MoveNext
BillingClient.QueryProductDetailsParamsProductList
UnityPurchasing.ConnectToStoreAndFetchProducts
This only affects users whose billing initialisation fails persistently, which makes it hard to catch in testing. On a healthy device initialisation succeeds first time and the path never repeats.
Issues you vote on will appear here