001/******************************************************************************* 002 * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved. 003 * 004 * This file is part of AVPKit. 005 * 006 * AVPKit is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * AVPKit is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with AVPKit. If not, see <http://www.gnu.org/licenses/>. 018 *******************************************************************************/ 019 020package com.avpkit.ferry; 021 022import java.lang.ref.WeakReference; 023import java.nio.ByteBuffer; 024import java.util.concurrent.atomic.AtomicLong; 025 026import com.avpkit.ferry.FerryJNI; 027 028/** 029 * Returned by {@link IBuffer#getByteBuffer(int, int, java.util.concurrent.atomic.AtomicReference)} 030 * for users that want to explicitly manage when the returned {@link java.nio.ByteBuffer} 031 * is released. 032 * <p> 033 * This class creates a {@link WeakReference} that Ferry classes will use for 034 * memory management. We do this to avoid relying on Java's finalizer thread to 035 * keep up and instead make every new native allocation first release any 036 * unreachable objects. 037 * </p><p> 038 * Most times these objects are managed behind the scenes when you 039 * call {@link RefCounted#delete()}. But when we return 040 * {@link java.nio.ByteBuffer} objects, there is no equivalent of 041 * delete(), so this object can be used if you want to explicitly control 042 * when the {@link ByteBuffer}'s underlying native memory is freed. 043 * </p> 044 * 045 */ 046public final class JNIReference extends WeakReference<Object> 047{ 048 private final AtomicLong mSwigCPtr = new AtomicLong(0); 049 050 // This memory manager will outlive the Java object we're referencing; that 051 // means this class will sometimes show up as a potential leak, but trust us 052 // here; ignore all the refs to here and see who else is holding the ref to 053 // the JNIMemoryAllocator and that's your likely leak culprit (if we didn't 054 // do this, then you'd get no indication of who is leaking your native object 055 // so stop complaining now). 056 private volatile JNIMemoryAllocator mMemAllocator; 057 058 private final boolean mIsFerryObject; 059 060 private final AtomicLong mJavaRefCount; 061 062 // A static memory allocator that is only constructed once 063 private static final JNIMemoryAllocator sMemAllocator = new JNIMemoryAllocator(); 064 private final boolean sUseStaticMemAllocator = true; 065 066 // Only turn this to true if you need memory debugging on. Given how 067 // hot this code is, remembering debug info is an unnecessary extra 068 // step. 069 private static volatile boolean mMemoryDebugging = false; 070 /* package */ static void setMemoryDebugging(boolean value) { 071 mMemoryDebugging = value; 072 } 073 /* package */ static boolean isMemoryDebugging() { 074 return mMemoryDebugging; 075 } 076 private static class DebugInfo { 077 private final int mHashCode; 078 private final Class<? extends Object> mClass; 079 public DebugInfo(Object aObject) 080 { 081 mClass = aObject.getClass(); 082 mHashCode = aObject.hashCode(); 083 } 084 public Class<? extends Object> getObjectClass() 085 { 086 return mClass; 087 } 088 public int getObjectHashCode() 089 { 090 return mHashCode; 091 } 092 } 093 final private DebugInfo mDebugInfo; 094 private JNIReference( 095 final Object proxy, 096 final Object aReferent, 097 final long nativeVal, 098 final boolean isFerry, 099 final AtomicLong javaRefCount) 100 { 101 super(aReferent, JNIMemoryManager.getMgr().getQueue()); 102 mIsFerryObject = isFerry; 103 mJavaRefCount = javaRefCount; 104 mSwigCPtr.set(nativeVal); 105 if (mMemoryDebugging) { 106 mDebugInfo = new DebugInfo(proxy); 107 } else { 108 mDebugInfo = null; 109 } 110 JNIMemoryManager.MemoryModel model = JNIMemoryManager.getMemoryModel(); 111 if (model == JNIMemoryManager.MemoryModel.JAVA_DIRECT_BUFFERS || 112 model == JNIMemoryManager.MemoryModel.NATIVE_BUFFERS) 113 // don't use our allocators 114 return; 115 if (mJavaRefCount.get() == 1 && 116 FerryJNI.RefCounted_getCurrentNativeRefCount(nativeVal, null) == 1) 117 { 118 // it's only safe to set the allocator if you're the only reference 119 // holder. Otherwise 120 // we default to having a null allocator, and allocations are anonymous, 121 // but at least 122 // won't crash under heavy multi-threaded situations. 123 if (sUseStaticMemAllocator) 124 mMemAllocator = sMemAllocator; 125 else 126 mMemAllocator = new JNIMemoryAllocator(); 127 // we are the only owner of this object; tell it we're the object it can 128 // allocate from 129 JNIMemoryAllocator.setAllocator(nativeVal, mMemAllocator); 130 } 131 else 132 { 133 // This creates a Strong reference to the allocator currently being used 134 // so that if the Java proxy object the native object depended upon goes 135 // away, the memory manager it was using stays around until this reference 136 // is killed 137 mMemAllocator = JNIMemoryAllocator.getAllocator(nativeVal); 138 } 139 } 140 141 /** 142 * Returns the {@link JNIMemoryManager} we're using. 143 * 144 * @return the manager 145 */ 146 static JNIMemoryManager getMgr() 147 { 148 return JNIMemoryManager.getMgr(); 149 } 150 151 static JNIReference createReference(Object proxy, 152 Object aReferent, long swigCPtr, 153 boolean isFerry, AtomicLong javaRefCount) 154 { 155 // Clear out any pending native objects 156 JNIMemoryManager.getMgr().gcInternal(); 157 158 JNIReference ref = new JNIReference( 159 proxy, aReferent, 160 swigCPtr, isFerry, javaRefCount); 161 JNIMemoryManager.getMgr().addReference(ref); 162 //System.err.println("added : "+ref+"; "+swigCPtr+" ("+isFerry+")"); 163 return ref; 164 } 165 static JNIReference createReference(Object proxy, 166 Object aReferent, long swigCPtr, 167 AtomicLong javaRefCount) 168 { 169 return createReference(proxy, aReferent, swigCPtr, true, javaRefCount); 170 } 171 static JNIReference createNonFerryReference( 172 Object proxy, Object aReferent, 173 long swigCPtr, AtomicLong javaRefCount) 174 { 175 return createReference(proxy, aReferent, swigCPtr, false, javaRefCount); 176 } 177 178 179 /** 180 * Explicitly deletes the underlying native storage used by 181 * the object this object references. The underlying native 182 * object is now no long valid, and attempts to use it could 183 * cause unspecified behavior. 184 * 185 */ 186 public void delete() 187 { 188 // acquire lock for minimum time 189 final long swigPtr = mSwigCPtr.getAndSet(0); 190 if (swigPtr != 0) 191 { 192 if (mJavaRefCount.decrementAndGet() == 0) 193 { 194 // log.debug("deleting: {}; {}", this, mSwigCPtr); 195 FerryJNI.RefCounted_release(swigPtr, null); 196 } 197 // Free the memory manager we use 198 mMemAllocator = null; 199 } 200 201 } 202 203 /** 204 * Returns true if this object derived from {@link RefCounted}. 205 * @return true if a ferry object. 206 */ 207 boolean isFerryObject() 208 { 209 return mIsFerryObject; 210 } 211 212 /** 213 * Returns true if the underlying reference has had {@link #delete()} called. 214 * @return True if {@link #delete()} was called. 215 */ 216 boolean isDeleted() 217 { 218 return mSwigCPtr.get() == 0; 219 } 220 /** 221 * Creates a string representation of this reference. If the underlying 222 * object this reference points to has been deleted, then "native" will 223 * be zero. If {@link JNIMemoryManager#isMemoryDebugging()} is true, 224 * then the class and hashcode of the object this reference points to (or 225 * used to point to) is also printed. 226 * <p> 227 * If the reference still points to an actual object, we will also print 228 * the contents of that object. It may return "null" in which case the 229 * underlying object is no longer reachable, but if native != 0, it means 230 * it has not yet been collected by Ferry. 231 * </p> 232 */ 233 @Override 234 public String toString() 235 { 236 final StringBuilder builder = new StringBuilder(); 237 builder.append(super.toString()); 238 builder.append("["); 239 builder.append("native=").append(mSwigCPtr.get()).append(";"); 240 if (mDebugInfo != null) { 241 builder.append("proxyClass=").append(mDebugInfo.getObjectClass().getCanonicalName()).append(";"); 242 builder.append("hashCode=").append(mDebugInfo.getObjectHashCode()).append(";"); 243 } 244 builder.append("object=[").append(get()).append("];"); 245 builder.append("];"); 246 247 return builder.toString(); 248 } 249}