Use soft references in array pool to prevent some memory crashes
Also log if there isn't enough memory
This commit is contained in:
@@ -1439,6 +1439,14 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> showPoolInsufficientMemoryWarning = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "If enabled, a chat message will be displayed if DH detects \n"
|
||||
+ "that any pooled objects have been garbage collected. \n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> showReplayWarningOnStartup = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
|
||||
+8
-2
@@ -2,10 +2,10 @@ package com.seibel.distanthorizons.core.pooling;
|
||||
|
||||
import com.seibel.distanthorizons.core.util.ListUtil;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,7 @@ public class PhantomArrayListCheckout implements AutoCloseable
|
||||
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
|
||||
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
|
||||
private final ArrayList<LongArrayList> longArrayLists = new ArrayList<>();
|
||||
private final ArrayList<SoftReference<LongArrayList>> longArrayRefLists = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +44,11 @@ public class PhantomArrayListCheckout implements AutoCloseable
|
||||
|
||||
public void addByteArrayList(ByteArrayList list) { this.byteArrayLists.add(list); }
|
||||
public void addShortArrayList(ShortArrayList list) { this.shortArrayLists.add(list); }
|
||||
public void addLongArrayList(LongArrayList list) { this.longArrayLists.add(list); }
|
||||
public void addLongArrayListRef(LongArrayList list, SoftReference<LongArrayList> listRef)
|
||||
{
|
||||
this.longArrayLists.add(list);
|
||||
this.longArrayRefLists.add(listRef);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +84,7 @@ public class PhantomArrayListCheckout implements AutoCloseable
|
||||
public ArrayList<ByteArrayList> getAllByteArrays() { return this.byteArrayLists; }
|
||||
public ArrayList<ShortArrayList> getAllShortArrays() { return this.shortArrayLists; }
|
||||
public ArrayList<LongArrayList> getAllLongArrays() { return this.longArrayLists; }
|
||||
public ArrayList<SoftReference<LongArrayList>> getAllLongArrayRefs() { return this.longArrayRefLists; }
|
||||
|
||||
|
||||
|
||||
|
||||
+136
-26
@@ -1,25 +1,29 @@
|
||||
package com.seibel.distanthorizons.core.pooling;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -57,6 +61,9 @@ public class PhantomArrayListPool
|
||||
private static final boolean LOG_ARRAY_RECOVERY = false;
|
||||
|
||||
|
||||
private static boolean lowMemoryWarningLogged = false;
|
||||
|
||||
|
||||
|
||||
/** used for debugging and tracking what the pool contains */
|
||||
public final String name;
|
||||
@@ -70,7 +77,7 @@ public class PhantomArrayListPool
|
||||
|
||||
private final ConcurrentLinkedQueue<ByteArrayList> pooledByteArrays = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentLinkedQueue<ShortArrayList> pooledShortArrays = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentLinkedQueue<LongArrayList> pooledLongArrays = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentLinkedQueue<SoftReference<LongArrayList>> pooledLongArrays = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/** counts how many byte arrays have been created by this pool */
|
||||
private final AtomicInteger totalByteArrayCountRef = new AtomicInteger(0);
|
||||
@@ -86,6 +93,9 @@ public class PhantomArrayListPool
|
||||
/** used for debugging, represents an estimate for how many bytes the long[] pool contains */
|
||||
private long lastLongPoolSizeInBytes = -1;
|
||||
|
||||
/** For pools backed by {@link SoftReference}'s we may need to decrease the size when elements are garbage collected */
|
||||
private boolean clearLastRefPoolSizes = false;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
@@ -194,7 +204,11 @@ public class PhantomArrayListPool
|
||||
// long
|
||||
for (int i = 0; i < longArrayCount; i++)
|
||||
{
|
||||
checkout.addLongArrayList(getPooledArray(this.pooledLongArrays, () -> this.createEmptyLongArrayList()));
|
||||
addRefPooledArray(
|
||||
this.pooledLongArrays,
|
||||
this::createEmptyLongArrayList,
|
||||
this::onLongArrayListGarbageCollected,
|
||||
checkout::addLongArrayListRef);
|
||||
}
|
||||
|
||||
return checkout;
|
||||
@@ -215,7 +229,7 @@ public class PhantomArrayListPool
|
||||
this.totalShortArrayCountRef.getAndIncrement();
|
||||
return new ShortArrayList(0);
|
||||
}
|
||||
public LongArrayList createEmptyLongArrayList()
|
||||
private LongArrayList createEmptyLongArrayList()
|
||||
{
|
||||
//LOGGER.error("created new long array");
|
||||
this.totalLongArrayCountRef.getAndIncrement();
|
||||
@@ -223,7 +237,17 @@ public class PhantomArrayListPool
|
||||
}
|
||||
|
||||
|
||||
// internal pool handler //
|
||||
// garbage collection handlers //
|
||||
|
||||
/** should only happen if Java doesn't have enough memory */
|
||||
private void onLongArrayListGarbageCollected()
|
||||
{
|
||||
this.clearLastRefPoolSizes = true;
|
||||
this.totalLongArrayCountRef.getAndDecrement();
|
||||
}
|
||||
|
||||
|
||||
// internal pool handlers //
|
||||
|
||||
private static <T extends List<?>> T getPooledArray(ConcurrentLinkedQueue<T> pool, Supplier<T> emptyArrayCreatorFunc)
|
||||
{
|
||||
@@ -239,6 +263,63 @@ public class PhantomArrayListPool
|
||||
return emptyArrayCreatorFunc.get();
|
||||
}
|
||||
}
|
||||
private static <T extends List<?>> void addRefPooledArray(
|
||||
ConcurrentLinkedQueue<SoftReference<T>> arrayPool,
|
||||
Supplier<T> emptyArrayCreatorFunc,
|
||||
Runnable arrayGarbageCollectedFunc,
|
||||
BiConsumer<T, SoftReference<T>> putArrayFunc)
|
||||
{
|
||||
T array = null;
|
||||
SoftReference<T> arrayRef = arrayPool.poll();
|
||||
|
||||
// find the first non-null pooled array
|
||||
while (arrayRef != null && array == null)
|
||||
{
|
||||
array = arrayRef.get();
|
||||
if (array == null)
|
||||
{
|
||||
// this reference is pointing to null,
|
||||
// the array must have been garbage collected,
|
||||
// that means we don't have enough memory
|
||||
if (!lowMemoryWarningLogged)
|
||||
{
|
||||
lowMemoryWarningLogged = true;
|
||||
|
||||
// orange text
|
||||
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
|
||||
"This may cause stuttering or crashing. \n" +
|
||||
"Either: your allocated memory isn't high enough, \n" +
|
||||
"your DH CPU preset is too high, or your DH quality preset is too high.";
|
||||
|
||||
LOGGER.warn(message);
|
||||
if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get())
|
||||
{
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(message);
|
||||
}
|
||||
}
|
||||
|
||||
arrayGarbageCollectedFunc.run();
|
||||
|
||||
// try the next reference
|
||||
arrayRef = arrayPool.poll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (array != null)
|
||||
{
|
||||
LodUtil.assertTrue(arrayRef != null, "How did we get an array without it's reference?");
|
||||
array.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// no pooled sources exist
|
||||
array = emptyArrayCreatorFunc.get();
|
||||
arrayRef = new SoftReference<>(array);
|
||||
}
|
||||
|
||||
putArrayFunc.accept(array, arrayRef);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -260,7 +341,7 @@ public class PhantomArrayListPool
|
||||
|
||||
this.pooledByteArrays.addAll(checkout.getAllByteArrays());
|
||||
this.pooledShortArrays.addAll(checkout.getAllShortArrays());
|
||||
this.pooledLongArrays.addAll(checkout.getAllLongArrays());
|
||||
this.pooledLongArrays.addAll(checkout.getAllLongArrayRefs());
|
||||
|
||||
//LOGGER.info("Returned ["+checkout.byteArrayLists.size()+"/"+this.pooledByteArrays.size()+"] bytes and ["+checkout.longArrayLists.size()+"/"+this.pooledLongArrays.size()+"] longs.");\
|
||||
}
|
||||
@@ -379,7 +460,12 @@ public class PhantomArrayListPool
|
||||
this.lastShortPoolSizeInBytes = Math.max(shortPoolByteSize, this.lastShortPoolSizeInBytes);
|
||||
|
||||
// long
|
||||
long longPoolByteSize = estimateMemoryUsage(this.pooledLongArrays, Long.BYTES);
|
||||
if (this.clearLastRefPoolSizes)
|
||||
{
|
||||
this.lastLongPoolSizeInBytes = 0;
|
||||
this.clearLastRefPoolSizes = false;
|
||||
}
|
||||
long longPoolByteSize = estimateRefMemoryUsage(this.pooledLongArrays, Long.BYTES);
|
||||
this.lastLongPoolSizeInBytes = Math.max(longPoolByteSize, this.lastLongPoolSizeInBytes);
|
||||
}
|
||||
|
||||
@@ -391,29 +477,53 @@ public class PhantomArrayListPool
|
||||
// Object overhead + capacity of underlying array * size of Long (8 bytes)
|
||||
long overhead = Byte.SIZE * 4;
|
||||
|
||||
long elementCount;
|
||||
if (array instanceof ByteArrayList)
|
||||
{
|
||||
elementCount = ((ByteArrayList)array).elements().length;
|
||||
}
|
||||
else if (array instanceof ShortArrayList)
|
||||
{
|
||||
elementCount = ((ShortArrayList)array).elements().length;
|
||||
}
|
||||
else if (array instanceof LongArrayList)
|
||||
{
|
||||
elementCount = ((LongArrayList)array).elements().length;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedOperationException("Not implemented for type ["+array.getClass().getSimpleName()+"].");
|
||||
}
|
||||
|
||||
long elementCount = getCollectionCount(array);
|
||||
long arraySize = elementCount * elementSizeInBytes;
|
||||
longByteSize += overhead + arraySize;
|
||||
}
|
||||
return longByteSize;
|
||||
}
|
||||
private static <T extends Collection<?>> long estimateRefMemoryUsage(ConcurrentLinkedQueue<SoftReference<T>> pool, long elementSizeInBytes)
|
||||
{
|
||||
long longByteSize = 0;
|
||||
for (SoftReference<T> arrayRef : pool)
|
||||
{
|
||||
// Object overhead + capacity of underlying array * size of Long (8 bytes)
|
||||
long overhead = Byte.SIZE * 4;
|
||||
T array = arrayRef.get();
|
||||
if (array == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
long elementCount = getCollectionCount(array);
|
||||
long arraySize = elementCount * elementSizeInBytes;
|
||||
longByteSize += overhead + arraySize;
|
||||
}
|
||||
return longByteSize;
|
||||
}
|
||||
private static long getCollectionCount(@NotNull Collection<?> array)
|
||||
{
|
||||
long elementCount;
|
||||
if (array instanceof ByteArrayList)
|
||||
{
|
||||
elementCount = ((ByteArrayList)array).elements().length;
|
||||
}
|
||||
else if (array instanceof ShortArrayList)
|
||||
{
|
||||
elementCount = ((ShortArrayList)array).elements().length;
|
||||
}
|
||||
else if (array instanceof LongArrayList)
|
||||
{
|
||||
elementCount = ((LongArrayList)array).elements().length;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedOperationException("Not implemented for type ["+array.getClass().getSimpleName()+"].");
|
||||
}
|
||||
|
||||
return elementCount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -643,7 +643,11 @@
|
||||
"distanthorizons.config.common.logging.warning":
|
||||
"Warnings",
|
||||
"distanthorizons.config.common.logging.warning.showLowMemoryWarningOnStartup":
|
||||
"Show Low Memory Warning",
|
||||
"Show Low Memory Warning On Startup",
|
||||
"distanthorizons.config.common.logging.warning.showPoolInsufficientMemoryWarning":
|
||||
"Show Pool Insufficient Memory Warning",
|
||||
"distanthorizons.config.common.logging.warning.showPoolInsufficientMemoryWarning.@tooltip":
|
||||
"If DH detects that pooled objects are being garbage collected this will send a chat warning.",
|
||||
"distanthorizons.config.common.logging.warning.showReplayWarningOnStartup":
|
||||
"Show Replay Warning",
|
||||
"distanthorizons.config.common.logging.warning.showUpdateQueueOverloadedChatWarning":
|
||||
|
||||
Reference in New Issue
Block a user