/*
 * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.graalvm.libgraal.jni;

import static org.graalvm.libgraal.jni.JNIUtil.DeleteGlobalRef;
import static org.graalvm.libgraal.jni.JNIUtil.NewGlobalRef;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.graalvm.compiler.debug.Assertions;
import org.graalvm.libgraal.jni.JNI.JNIEnv;
import org.graalvm.libgraal.jni.JNI.JObject;
import org.graalvm.word.WordFactory;

Encapsulates a JNI handle to an object in the HotSpot heap. Depending on which constructor is used, the handle is either local to a JNILibGraalScope and thus invalid once the scope exits or a global JNI handle that is only released sometime after the HSObject dies.
/** * Encapsulates a JNI handle to an object in the HotSpot heap. Depending on which constructor is * used, the handle is either local to a {@link JNILibGraalScope} and thus invalid once the scope * exits or a global JNI handle that is only released sometime after the {@link HSObject} dies. */
public abstract class HSObject {
JNI handle to the HotSpot object.
/** * JNI handle to the HotSpot object. */
private final JObject handle;
Cleaner for Global Reference.
/** * Cleaner for Global Reference. */
private final Cleaner cleaner;
Link to next the next scope local object. The head of the list is in JNILibGraalScope.locals. The handle of a scope local object is only valid for the lifetime of a JNILibGraalScope. A self-reference (i.e. this.next == this) denotes an object whose JNILibGraalScope has closed. This field is null for a non-scope local object.
/** * Link to next the next scope local object. The head of the list is in * {@link JNILibGraalScope#locals}. The handle of a scope local object is only valid for the * lifetime of a {@link JNILibGraalScope}. A self-reference (i.e. {@code this.next == this}) * denotes an object whose {@link JNILibGraalScope} has closed. * * This field is {@code null} for a non-scope local object. */
private HSObject next;
Creates an object encapsulating a handle whose lifetime is determined by this object.
/** * Creates an object encapsulating a {@code handle} whose lifetime is determined by this object. */
protected HSObject(JNIEnv env, JObject handle) { cleanHandles(env); if (checkingGlobalDuplicates()) { checkNonExistingGlobalReference(env, handle); } this.handle = NewGlobalRef(env, handle, this.getClass().getSimpleName()); cleaner = new Cleaner(this, this.handle); CLEANERS.add(cleaner); next = null; } private static boolean checkingGlobalDuplicates() { return Assertions.assertionsEnabled() || JNIUtil.tracingAt(1); }
Creates an object encapsulating a handle whose lifetime is limited to scope. Once scope.close() is called, any attempt to use the handle will result in an IllegalArgumentException.
/** * Creates an object encapsulating a {@code handle} whose lifetime is limited to {@code scope}. * Once {@code scope.close()} is called, any attempt to {@linkplain #getHandle() use} the handle * will result in an {@link IllegalArgumentException}. */
protected <T extends Enum<T>> HSObject(JNILibGraalScope<T> scope, JObject handle) { this.handle = handle; next = scope.locals; scope.locals = this; cleaner = null; }
Invalidates the objects in the list starting at head such that subsequently calling getHandle() on any of the objects results in an IllegalArgumentException.
Returns:the number of objects in the list
/** * Invalidates the objects in the list starting at {@code head} such that subsequently calling * {@link #getHandle()} on any of the objects results in an {@link IllegalArgumentException}. * * @return the number of objects in the list */
static int invalidate(HSObject head) { HSObject o = head; int count = 0; while (o != null) { HSObject next = o.next; // Makes the handle now invalid. o.next = o; o = next; count++; } return count; } public JObject getHandle() { if (next == this) { throw new IllegalArgumentException("Reclaimed JNI reference: " + this); } return handle; } @Override public String toString() { return String.format("%s[0x%x]", getClass().getSimpleName(), handle.rawValue()); } public void release(JNIEnv env) { if (cleaner != null) { assert next == null || next == this; this.next = this; cleaner.clean(env); } }
Processes CLEANERS_QUEUE to release any handles whose objects are now unreachable.
/** * Processes {@link #CLEANERS_QUEUE} to release any handles whose objects are now unreachable. */
public static void cleanHandles(JNIEnv env) { Cleaner cleaner; while ((cleaner = (Cleaner) CLEANERS_QUEUE.poll()) != null) { cleaner.clean(env); } } private static void checkNonExistingGlobalReference(JNIEnv env, JObject handle) { for (Cleaner cleaner : CLEANERS) { synchronized (cleaner) { if (cleaner.handle.isNonNull() && JNIUtil.IsSameObject(env, handle, cleaner.handle)) { throw new IllegalArgumentException("Global JNI handle already exists for object referenced by " + handle.rawValue()); } } } }
Strong references to the PhantomReference objects.
/** * Strong references to the {@link PhantomReference} objects. */
private static final Set<Cleaner> CLEANERS = Collections.newSetFromMap(new ConcurrentHashMap<>());
Queue into which a Cleaner is enqueued when its HSObject referent becomes unreachable.
/** * Queue into which a {@link Cleaner} is enqueued when its {@link HSObject} referent becomes * unreachable. */
private static final ReferenceQueue<HSObject> CLEANERS_QUEUE = new ReferenceQueue<>(); private static final class Cleaner extends PhantomReference<HSObject> { private JObject handle; Cleaner(HSObject referent, JObject handle) { super(referent, CLEANERS_QUEUE); this.handle = handle; } void clean(JNIEnv env) { if (CLEANERS.remove(this)) { if (checkingGlobalDuplicates()) { synchronized (this) { DeleteGlobalRef(env, handle); handle = WordFactory.nullPointer(); } } else { DeleteGlobalRef(env, handle); } } } } }