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.util.HashSet; 023import java.util.Set; 024import java.util.concurrent.locks.ReentrantLock; 025 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * Internal Only. 031 * <p> 032 * This object allocates large memory chunks and returns them to native code. 033 * The native memory then pins raw bytes based on the byte[]s returned here. The 034 * net effect is that Java ends up thinking it actually allocated the memory, 035 * but since a {@link RefCounted} object will also maintain a reference to this 036 * allocator, you can use this to detect instances of 'leaked' references in 037 * your Java code. 038 * </p> 039 * <p> 040 * This function is called DIRECTLY from native code; names of methods MUST NOT 041 * CHANGE. 042 * </p> 043 * 044 * @author aclarke 045 * 046 */ 047 048public final class JNIMemoryAllocator 049{ 050 private static final Logger log = LoggerFactory 051 .getLogger(JNIMemoryAllocator.class); 052 053 // don't allocate this unless we malloc; most classes don't even 054 // touch this memory manager with a 100 foot pole 055 final private Set<byte[]> mBuffers = 056 new HashSet<byte[]>(); 057 final private ReentrantLock mLock = 058 new ReentrantLock(); 059 final static private int MAX_ALLOCATION_ATTEMPTS = 5; 060 final static private double FALLBACK_TIME_DECAY = 1.5; 061 final static private boolean SHOULD_RETRY_FAILED_ALLOCS = true; 062 063 private void addToBuffer(byte[] mem) 064 { 065 mLock.lock(); 066 try 067 { 068 if (!mBuffers.add(mem)) 069 { 070 assert false : "buffers already added"; 071 } 072 } 073 finally 074 { 075 mLock.unlock(); 076 } 077 } 078 079 private void removeFromBuffer(byte[] mem) 080 { 081 mLock.lock(); 082 try 083 { 084 if (!mBuffers.remove(mem)) 085 { 086 assert false : "buffer not in memory"; 087 } 088 } 089 finally 090 { 091 mLock.unlock(); 092 } 093 } 094 095 /** 096 * Not for use outside the package 097 */ 098 JNIMemoryAllocator() 099 { 100 101 } 102 103 /** 104 * Internal Only. Allocate a new block of bytes. Called from native code. 105 * <p> 106 * Will retry many times if it can't get memory, backing off 107 * in timeouts to get there. 108 * </p> 109 * <p> 110 * Callers must eventually call {@link #free(byte[])} when done 111 * with the bytes or a leak will result. 112 * </p> 113 * @param size 114 * # of bytes requested 115 * @return An array of size or more bytes long, or null on failure. 116 */ 117 118 public byte[] malloc(int size) 119 { 120 byte[] retval = null; 121 // first check the parachute 122 JNIMemoryParachute.getParachute().packChute(); 123 try 124 { 125 if (SHOULD_RETRY_FAILED_ALLOCS) 126 { 127 int allocationAttempts = 0; 128 int backoffTimeout = 10; // start at 10 milliseconds 129 while (true) 130 { 131 try 132 { 133 // log.debug("attempting malloc of size: {}", size); 134 retval = new byte[size]; 135 // log.debug("malloced block of size: {}", size); 136 // we succeed, so break out 137 break; 138 } 139 catch (final OutOfMemoryError e) 140 { 141 // try clearing our queue now and do it again. Why? 142 // because the first failure may have allowed us to 143 // catch a RefCounted no longer in use, and the second 144 // attempt may have freed that memory. 145 146 // do a JNI collect before the alloc 147 ++allocationAttempts; 148 if (allocationAttempts >= MAX_ALLOCATION_ATTEMPTS) 149 { 150 // try pulling our rip cord 151 JNIMemoryParachute.getParachute().pullCord(); 152 // do one last "hope gc" to free our own memory 153 JNIReference.getMgr().gcInternal(); 154 // and throw the error back to the native code 155 throw e; 156 } 157 158 log.debug("retrying ({}) allocation of {} bytes", 159 allocationAttempts, size); 160 try 161 { 162 // give the finalizer a chance 163 if (allocationAttempts <= 1) 164 { 165 // first just yield 166 Thread.yield(); 167 } 168 else 169 { 170 Thread.sleep(backoffTimeout); 171 // and slowly get longer... 172 backoffTimeout = (int) (backoffTimeout * FALLBACK_TIME_DECAY); 173 } 174 } 175 catch (InterruptedException e1) 176 { 177 // reset the interruption so underlying 178 // code can also interrupt 179 Thread.currentThread().interrupt(); 180 // and throw the error condition 181 throw e; 182 } 183 // do a JNI collect before the alloc 184 JNIReference.getMgr().gcInternal(); 185 } 186 } 187 } 188 else 189 { 190 retval = new byte[size]; 191 } 192 addToBuffer(retval); 193 retval[retval.length - 1] = 0; 194 195// log.debug("malloc: {}({}:{})", new Object[] 196// { 197// retval.hashCode(), retval.length, size 198// }); 199 200 } 201 catch (Throwable t) 202 { 203 // do not let an exception leak out since we go back to native code. 204 retval = null; 205 } 206 return retval; 207 } 208 209 /** 210 * Free memory allocated by the {@link #malloc(int)} method. 211 * Called from native code. 212 * 213 * @param mem 214 * the byes to be freed. 215 */ 216 217 public void free(byte[] mem) 218 { 219 removeFromBuffer(mem); 220// log.debug("free: {}({})", mem.hashCode(), mem.length); 221 } 222 223 /** 224 * Internal Only. Native method that tells a native objects (represented by the nativeObj 225 * long pointer val) that this JNIMemoryAllocator is being used to allocate 226 * it's large blocks of memory. 227 * <p> 228 * Follow that? No. That's OK... you really don't want to know. 229 * </p> 230 * 231 * @param nativeObj 232 * A C pointer (a la swig). 233 * @param mgr 234 * Us. 235 */ 236 237 public native static void setAllocator(long nativeObj, JNIMemoryAllocator mgr); 238 239 /** 240 * Internal Only. Get the allocator for the underlying native pointer. 241 * 242 * @param nativeObj 243 * The native pointer. 244 * @return The allocator to use, or null. 245 */ 246 247 public native static JNIMemoryAllocator getAllocator(long nativeObj); 248 249}