Add BasicPhantomReference for debugging
This commit is contained in:
+172
@@ -1,9 +1,17 @@
|
|||||||
package com.seibel.distanthorizons.core.util.objects.pooling;
|
package com.seibel.distanthorizons.core.util.objects.pooling;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||||
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class PhantomLoggingHelper
|
public class PhantomLoggingHelper
|
||||||
@@ -57,4 +65,168 @@ public class PhantomLoggingHelper
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be quickly added to a {@link AutoCloseable} implementing
|
||||||
|
* class to confirm it's being properly closed.
|
||||||
|
*/
|
||||||
|
public static class BasicPhantomReference implements AutoCloseable
|
||||||
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
/** if enabled the number of GC'ed buffers will be logged */
|
||||||
|
private static final boolean LOG_PHANTOM_RECOVERY = true;
|
||||||
|
/**
|
||||||
|
* If enabled the GC'ed buffers allocation/upload stacks will be logged.
|
||||||
|
* Note: due to how the buffers are often run on the render thread,
|
||||||
|
* these stacks will likely only be of limited use.
|
||||||
|
* For more robust debugging it would likely be best to somehow track
|
||||||
|
* the stacks of where these calls are happening before they're queued
|
||||||
|
* for the render thread.
|
||||||
|
*/
|
||||||
|
private static final boolean LOG_PHANTOM_ALLOCATION_STACKS = true;
|
||||||
|
|
||||||
|
private static final int PHANTOM_REF_CHECK_TIME_IN_MS = 5 * 1000;
|
||||||
|
private static final ReferenceQueue<BasicPhantomReference> PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>();
|
||||||
|
private static final ConcurrentHashMap<PhantomReference<? extends BasicPhantomReference>, Class<?>> PHANTOM_TO_PARENT_CLASS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("BasicPhantom Cleanup");
|
||||||
|
|
||||||
|
|
||||||
|
private final Class<?> parentClass;
|
||||||
|
private final PhantomReference<? extends BasicPhantomReference> phantomReference;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
static { CLEANUP_THREAD.execute(() -> runPhantomReferenceCleanupLoop()); }
|
||||||
|
|
||||||
|
public BasicPhantomReference(Class<?> parentClass)
|
||||||
|
{
|
||||||
|
this.parentClass = parentClass;
|
||||||
|
this.phantomReference = new PhantomReference<>(this, PHANTOM_REFERENCE_QUEUE);
|
||||||
|
PHANTOM_TO_PARENT_CLASS.put(this.phantomReference, this.parentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
this.phantomReference.clear();
|
||||||
|
PHANTOM_TO_PARENT_CLASS.remove(this.phantomReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// static cleanup //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
private static void runPhantomReferenceCleanupLoop()
|
||||||
|
{
|
||||||
|
// these arrays are stored here so they don't have to be re-allocated each loop
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList = new ArrayList<>();
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> parentClassNameCountPairList = new ArrayList<>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
allocationStackTraceCountPairList.clear();
|
||||||
|
parentClassNameCountPairList.clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(PHANTOM_REF_CHECK_TIME_IN_MS);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignore) { }
|
||||||
|
|
||||||
|
int collectedCount = 0;
|
||||||
|
|
||||||
|
Reference<? extends BasicPhantomReference> phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
|
||||||
|
while (phantomRef != null)
|
||||||
|
{
|
||||||
|
// destroy the buffer if it hasn't been cleared yet
|
||||||
|
Class<?> parentClass = PHANTOM_TO_PARENT_CLASS.remove((PhantomReference<? extends BasicPhantomReference>)phantomRef); // cast to make IntelliJ happy
|
||||||
|
{
|
||||||
|
String parentClassName = "NULL";
|
||||||
|
if (parentClass != null)
|
||||||
|
{
|
||||||
|
parentClassName = parentClass.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
PhantomLoggingHelper.putAndIncrementTrackingString(parentClassName, parentClassNameCountPairList);
|
||||||
|
//LOGGER.info("Phantom collected for class: [" + parentClassName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//if (LOG_PHANTOM_ALLOCATION_STACKS) // stack trace shouldn't be null, but just in case
|
||||||
|
//{
|
||||||
|
// String stack = BUFFER_ID_TO_ALLOCATION_STRING.get(idRef);
|
||||||
|
// if (stack != null)
|
||||||
|
// {
|
||||||
|
// PhantomLoggingHelper.putAndIncrementTrackingString(stack, allocationStackTraceCountPairList);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
collectedCount++;
|
||||||
|
phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (LOG_PHANTOM_RECOVERY)
|
||||||
|
{
|
||||||
|
// we only want to log when something has been returned
|
||||||
|
if (collectedCount != 0)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Phantoms collected: ["+ F3Screen.NUMBER_FORMAT.format(collectedCount)+"].");
|
||||||
|
|
||||||
|
PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, parentClassNameCountPairList);
|
||||||
|
|
||||||
|
//// log stack traces if present
|
||||||
|
//if (LOG_PHANTOM_ALLOCATION_STACKS)
|
||||||
|
//{
|
||||||
|
// PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error in buffer cleanup thread: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user