
com.metsci.glimpse.util.ugly.CleanerUtils Maven / Gradle / Ivy
/*
* Copyright (c) 2020, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Metron, Inc. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.metsci.glimpse.util.ugly;
import static com.metsci.glimpse.util.ugly.UglyUtils.findClass;
import static com.metsci.glimpse.util.ugly.UglyUtils.firstSuccessfulReturnValue;
import static com.metsci.glimpse.util.ugly.UglyUtils.requireDeclaredMethod;
import java.lang.reflect.Method;
import java.util.concurrent.ThreadFactory;
/**
* Cleaners serve the same purpose as finalize() methods, with 2 subtle differences:
*
* - finalize() methods are better when resource disposal is non-trivial and/or slow
*
- The JVM does a better job of running Cleaners promptly
*
*
* When a registered Object becomes eligible to be GC-ed, its corresponding Cleaner gets
* magically triggered via the JVM's PhantomReference mechanism.
*
* It is easy to write code that uses Java 8 Cleaners, and it is easy to write code that
* uses Java 9+ Cleaners. However, it is painful to write code that can use either Java 8
* or Java 9+ Cleaners. The goal of this class is to encapsulate and hide that pain.
*
* Java 9+ supports creation of Cleaners that use specific {@link ThreadFactory}s. That
* capability is not supported by Java 8, and therefore not supported by this class.
*/
public class CleanerUtils
{
/**
* Like {@code java.lang.ref.Cleaner.Cleanable}, but available in Java 8.
*/
public static interface Cleanable
{
void clean( );
}
protected static interface CleanerImpl
{
Cleanable register( Object obj, Runnable action );
}
/**
* Cleaner impl for Java 8 JVMs.
*
* Also works for Java 9+ JVMs when {@link CleanerUtils} is on the classpath (not the modulepath).
*/
protected static class CleanerImpl1 implements CleanerImpl
{
protected final Method Cleaner_create;
protected final Method Cleaner_clean;
public CleanerImpl1( ) throws Exception
{
Class> Cleaner_class = findClass( "jdk.internal.ref.Cleaner", "sun.misc.Cleaner" );
this.Cleaner_create = requireDeclaredMethod( Cleaner_class, "create", Object.class, Runnable.class );
this.Cleaner_clean = requireDeclaredMethod( Cleaner_class, "clean" );
}
@Override
public Cleanable register( Object obj, Runnable action )
{
try
{
Object cleaner = this.Cleaner_create.invoke( null, obj, action );
return ( ) ->
{
try
{
this.Cleaner_clean.invoke( cleaner );
}
catch ( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
};
}
catch ( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
}
}
/**
* Cleaner impl for Java 9+ JVMs.
*/
protected static class CleanerImpl2 implements CleanerImpl
{
protected final Object cleaner;
protected final Method Cleaner_register;
protected final Method Cleanable_clean;
public CleanerImpl2( ) throws Exception
{
Class> Cleaner_class = Class.forName( "java.lang.ref.Cleaner" );
Method Cleaner_create = requireDeclaredMethod( Cleaner_class, "create" );
this.cleaner = Cleaner_create.invoke( null );
this.Cleaner_register = requireDeclaredMethod( Cleaner_class, "register", Object.class, Runnable.class );
Class> Cleanable_class = Class.forName( "java.lang.ref.Cleaner$Cleanable" );
this.Cleanable_clean = requireDeclaredMethod( Cleanable_class, "clean" );
}
@Override
public Cleanable register( Object obj, Runnable action )
{
try
{
Object cleanable = this.Cleaner_register.invoke( this.cleaner, obj, action );
return ( ) ->
{
try
{
this.Cleanable_clean.invoke( cleanable );
}
catch ( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
};
}
catch ( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
}
}
protected static final CleanerImpl cleanerImpl = firstSuccessfulReturnValue( CleanerImpl2::new,
CleanerImpl1::new );
/**
* IMPORTANT: {@code action} MUST NOT refer to {@code obj} ... if
* {@code action} does hold a reference to {@code obj}, it will prevent {@code obj}
* from becoming eligible for cleaning.
*
* A good way to avoid accidental references to {@code obj} is to avoid using a lambda,
* or even an inner class, for {@code action}. Use a static class instead:
*
* public class SomeClass
* {
* // Static class -- not inner class, not lambda
* private static class CleaningAction implements Runnable
* {
* private final Object thingNeededDuringCleanup1;
* private final Object thingNeededDuringCleanup2;
*
* public CleaningAction( Object thingNeededDuringCleanup1, Object thingNeededDuringCleanup2 )
* {
* this.thingNeededDuringCleanup1 = thingNeededDuringCleanup1;
* this.thingNeededDuringCleanup2 = thingNeededDuringCleanup2;
* }
*
* {@literal @}Override
* public void run( )
* {
* // Do cleanup
* }
* }
*
* private final Cleanable cleanable;
*
* public SomeClass( )
* {
* this.cleanable = UglyUtils.registerCleaner( this, new CleaningAction( ... ) );
* }
* }
*
*/
public static Cleanable registerCleaner( Object obj, Runnable action )
{
return cleanerImpl.register( obj, action );
}
}