All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.jwharm.javagi.interop.MemoryCleaner Maven / Gradle / Ivy

/* Java-GI - Java language bindings for GObject-Introspection-based libraries
 * Copyright (C) 2022-2023 Jan-Willem Harmannij
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see .
 */

package io.github.jwharm.javagi.interop;

import io.github.jwharm.javagi.base.Proxy;
import org.gnome.glib.GLib;
import org.gnome.glib.Type;

import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.ref.Cleaner;
import java.util.HashMap;
import java.util.Map;

/**
 * This class keeps a cache of all memory addresses for which a Proxy object
 * was created (except for GObject instances; those are handled in the
 * InstanceCache).
 * 

* When a new Proxy object is created, the reference count in the cache is * increased. When a Proxy object is garbage-collected, the reference count in * the cache is decreased. When the reference count is 0, the memory is * released using {@link GLib#free(MemorySegment)} or a specialized method. *

* When ownership of a memory address is passed to native code, the cleaner * will not free the memory. Ownership is enabled/disabled with * {@link #takeOwnership(MemorySegment)} and * {@link #yieldOwnership(MemorySegment)}. */ public class MemoryCleaner { private static final Map references = new HashMap<>(); private static final Cleaner CLEANER = Cleaner.create(); /** * Register the memory address of this proxy to be cleaned when the proxy * gets garbage-collected. * * @param proxy The Proxy object */ public static void register(Proxy proxy) { MemorySegment address = proxy.handle(); synchronized (references) { Cached cached = references.get(address); if (cached == null) { // Put the address in the cache var cleanable = CLEANER.register(proxy, new StructFinalizer(address)); references.put(address, new Cached(false, 1, null, null, cleanable)); } else { // Already in the cache: increase the refcount references.put(address, new Cached(false, cached.references + 1, cached.freeFunc, cached.boxedType, cached.cleanable)); } } } /** * Register a specialized cleanup function for this memory address, instead * of the default {@link GLib#free(MemorySegment)}. * * @param address the memory address * @param freeFunc the specialized cleanup function to call */ public static void setFreeFunc(MemorySegment address, String freeFunc) { synchronized (references) { Cached cached = references.get(address); if (cached != null) references.put(address, new Cached(cached.owned, cached.references, freeFunc, cached.boxedType, cached.cleanable)); } } /** * For a boxed type, {@code g_boxed_free(type, pointer)} will be used as * cleanup function. * * @param address the memory address * @param boxedType the boxed type */ public static void setBoxedType(MemorySegment address, Type boxedType) { synchronized (references) { Cached cached = references.get(address); if (cached != null) references.put(address, new Cached(cached.owned, cached.references, cached.freeFunc, boxedType, cached.cleanable)); } } /** * Take ownership of this memory address: when all proxy objects are * garbage-collected, the memory will automatically be released. * * @param address the memory address */ public static void takeOwnership(MemorySegment address) { synchronized (references) { Cached cached = references.get(address); if (cached != null) references.put(address, new Cached(true, cached.references, cached.freeFunc, cached.boxedType, cached.cleanable)); } } /** * Yield ownership of this memory address: when all proxy objects are * garbage-collected, the memory will not be released. * * @param address the memory address */ public static void yieldOwnership(MemorySegment address) { synchronized (references) { Cached cached = references.get(address); if (cached != null) references.put(address, new Cached(false, cached.references, cached.freeFunc, cached.boxedType, cached.cleanable)); } } /** * Run the {@link StructFinalizer} associated with this memory address, by * invoking {@link Cleaner.Cleanable#clean()}. * * @param address the memory address to free */ public static void free(MemorySegment address) { synchronized (references) { Cached cached = references.get(address); cached.cleanable.clean(); } } /** * This record type is cached for each memory address. * * @param owned whether this address is owned (should be cleaned) * @param references the number of references (active Proxy objects) for * this address * @param freeFunc an (optional) specialized function that will release * the native memory */ private record Cached(boolean owned, int references, String freeFunc, Type boxedType, Cleaner.Cleanable cleanable) { } /** * This callback is run by the {@link Cleaner} when a struct or union * instance has become unreachable, to free the native memory. */ private record StructFinalizer(MemorySegment address) implements Runnable { /** * This method is run by the {@link Cleaner} when the last Proxy object * for this memory address is garbage-collected. */ public void run() { Cached cached; synchronized (references) { cached = references.get(address); // When other references exist, decrease the refcount if (cached.references > 1) { references.put(address, new Cached(cached.owned, cached.references - 1, cached.freeFunc, cached.boxedType, cached.cleanable)); return; } // When no other references exist, remove the address from the // cache and free the memory references.remove(address); } // if we don't have ownership, we must not run free() if (!cached.owned) { return; } // run g_free if (cached.freeFunc == null) { GLib.free(address); return; } try { if (cached.boxedType != null) { // free boxed type Interop.downcallHandle( "g_boxed_free", FunctionDescriptor.ofVoid(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS), false ).invokeExact(cached.boxedType.getValue(), address); } else { // Run specialized free function Interop.downcallHandle( cached.freeFunc, FunctionDescriptor.ofVoid(ValueLayout.ADDRESS), false ).invokeExact(address); } } catch (Throwable e) { throw new AssertionError("Unexpected exception occurred: ", e); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy