diff --git a/src/main/java/com/seibel/lod/proxy/ClientProxy.java b/src/main/java/com/seibel/lod/proxy/ClientProxy.java index d63a10055..3119df935 100644 --- a/src/main/java/com/seibel/lod/proxy/ClientProxy.java +++ b/src/main/java/com/seibel/lod/proxy/ClientProxy.java @@ -34,6 +34,7 @@ import com.seibel.lod.objects.LodQuadTreeWorld; import com.seibel.lod.objects.RegionPos; import com.seibel.lod.render.LodNodeRenderer; import com.seibel.lod.util.LodUtil; +import com.seibel.lod.util.ObjectSizeCalculator; import net.minecraft.client.Minecraft; import net.minecraft.profiler.IProfiler; @@ -117,6 +118,12 @@ public class ClientProxy lodDim.move(new RegionPos(xOffset, zOffset)); } + // just here to prevent eclipse removing the imports when I save the file + ObjectSizeCalculator.getObjectSize(null); + + // uncomment once the LODs have fully generated to see the memory usage +// long size = ObjectSizeCalculator.getObjectSize(lodDim); +// LOGGER.info(size); // comment out when creating a release @@ -157,15 +164,15 @@ public class ClientProxy LodConfig.CLIENT.lodChunkRadiusMultiplier.set(12); LodConfig.CLIENT.fogDistance.set(FogDistance.FAR); - LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY); + LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.NEVER_DRAW_FOG); LodConfig.CLIENT.shadingMode.set(ShadingMode.DARKEN_SIDES); // LodConfig.CLIENT.brightnessMultiplier.set(1.0); // LodConfig.CLIENT.saturationMultiplier.set(1.0); - LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES); + LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.SURFACE); LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false); -// LOGGER.info(lodBufferBuilder.numberOfChunksWaitingToGenerate.get()); + LodConfig.CLIENT.numberOfWorldGenerationThreads.set(16); } @@ -176,7 +183,6 @@ public class ClientProxy @SubscribeEvent public void chunkLoadEvent(ChunkEvent.Load event) { - //lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld()); lodNodeBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld()); } diff --git a/src/main/java/com/seibel/lod/util/ObjectSizeCalculator.java b/src/main/java/com/seibel/lod/util/ObjectSizeCalculator.java new file mode 100644 index 000000000..14873fa6e --- /dev/null +++ b/src/main/java/com/seibel/lod/util/ObjectSizeCalculator.java @@ -0,0 +1,441 @@ +package com.seibel.lod.util; + +/** + * found on stack overflow here: + * https://stackoverflow.com/a/29536817/14866233 + * + * With the code found here: + * https://github.com/twitter-archive/commons/blob/master/src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java + * + * James Seibel - 8-13-2021 +*/ + +//================================================================================================= +//Copyright 2011 Twitter, Inc. +//------------------------------------------------------------------------------------------------- +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this work except in compliance with the License. +//You may obtain a copy of the License in the LICENSE file, or at: +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. +//================================================================================================= + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Sets; + +/** +* Contains utility methods for calculating the memory usage of objects. It +* only works on the HotSpot JVM, and infers the actual memory layout (32 bit +* vs. 64 bit word size, compressed object pointers vs. uncompressed) from +* best available indicators. It can reliably detect a 32 bit vs. 64 bit JVM. +* It can only make an educated guess at whether compressed OOPs are used, +* though; specifically, it knows what the JVM's default choice of OOP +* compression would be based on HotSpot version and maximum heap sizes, but if +* the choice is explicitly overridden with the -XX:{+|-}UseCompressedOops command line +* switch, it can not detect +* this fact and will report incorrect sizes, as it will presume the default JVM +* behavior. +* +* @author Attila Szegedi +*/ +public class ObjectSizeCalculator { + +/** +* Describes constant memory overheads for various constructs in a JVM implementation. +*/ +public interface MemoryLayoutSpecification { + + /** + * Returns the fixed overhead of an array of any type or length in this JVM. + * + * @return the fixed overhead of an array. + */ + int getArrayHeaderSize(); + + /** + * Returns the fixed overhead of for any {@link Object} subclass in this JVM. + * + * @return the fixed overhead of any object. + */ + int getObjectHeaderSize(); + + /** + * Returns the quantum field size for a field owned by an object in this JVM. + * + * @return the quantum field size for an object. + */ + int getObjectPadding(); + + /** + * Returns the fixed size of an object reference in this JVM. + * + * @return the size of all object references. + */ + int getReferenceSize(); + + /** + * Returns the quantum field size for a field owned by one of an object's ancestor superclasses + * in this JVM. + * + * @return the quantum field size for a superclass field. + */ + int getSuperclassFieldPadding(); +} + +private static class CurrentLayout { + private static final MemoryLayoutSpecification SPEC = + getEffectiveMemoryLayoutSpecification(); +} + +/** +* Given an object, returns the total allocated size, in bytes, of the object +* and all other objects reachable from it. Attempts to to detect the current JVM memory layout, +* but may fail with {@link UnsupportedOperationException}; +* +* @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do +* anything special, it measures the size of all objects +* reachable through it (which will include its class loader, and by +* extension, all other Class objects loaded by +* the same loader, and all the parent class loaders). It doesn't provide the +* size of the static fields in the JVM class that the Class object +* represents. +* @return the total allocated size of the object and all other objects it +* retains. +* @throws UnsupportedOperationException if the current vm memory layout cannot be detected. +*/ +public static long getObjectSize(Object obj) throws UnsupportedOperationException { + return obj == null ? 0 : new ObjectSizeCalculator(CurrentLayout.SPEC).calculateObjectSize(obj); +} + +// Fixed object header size for arrays. +private final int arrayHeaderSize; +// Fixed object header size for non-array objects. +private final int objectHeaderSize; +// Padding for the object size - if the object size is not an exact multiple +// of this, it is padded to the next multiple. +private final int objectPadding; +// Size of reference (pointer) fields. +private final int referenceSize; +// Padding for the fields of superclass before fields of subclasses are +// added. +private final int superclassFieldPadding; + +private final LoadingCache, ClassSizeInfo> classSizeInfos = + CacheBuilder.newBuilder().build(new CacheLoader, ClassSizeInfo>() { + @Override + public ClassSizeInfo load(Class clazz) { + return new ClassSizeInfo(clazz); + } + }); + + +private final Set alreadyVisited = Sets.newIdentityHashSet(); +private final Deque pending = new ArrayDeque(16 * 1024); +private long size; + +/** +* Creates an object size calculator that can calculate object sizes for a given +* {@code memoryLayoutSpecification}. +* +* @param memoryLayoutSpecification a description of the JVM memory layout. +*/ +public ObjectSizeCalculator(MemoryLayoutSpecification memoryLayoutSpecification) { + Preconditions.checkNotNull(memoryLayoutSpecification); + arrayHeaderSize = memoryLayoutSpecification.getArrayHeaderSize(); + objectHeaderSize = memoryLayoutSpecification.getObjectHeaderSize(); + objectPadding = memoryLayoutSpecification.getObjectPadding(); + referenceSize = memoryLayoutSpecification.getReferenceSize(); + superclassFieldPadding = memoryLayoutSpecification.getSuperclassFieldPadding(); +} + +/** +* Given an object, returns the total allocated size, in bytes, of the object +* and all other objects reachable from it. +* +* @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do +* anything special, it measures the size of all objects +* reachable through it (which will include its class loader, and by +* extension, all other Class objects loaded by +* the same loader, and all the parent class loaders). It doesn't provide the +* size of the static fields in the JVM class that the Class object +* represents. +* @return the total allocated size of the object and all other objects it +* retains. +*/ +public synchronized long calculateObjectSize(Object obj) { + // Breadth-first traversal instead of naive depth-first with recursive + // implementation, so we don't blow the stack traversing long linked lists. + try { + for (;;) { + visit(obj); + if (pending.isEmpty()) { + return size; + } + obj = pending.removeFirst(); + } + } finally { + alreadyVisited.clear(); + pending.clear(); + size = 0; + } +} + +private void visit(Object obj) { + if (alreadyVisited.contains(obj)) { + return; + } + final Class clazz = obj.getClass(); + if (clazz == ArrayElementsVisitor.class) { + ((ArrayElementsVisitor) obj).visit(this); + } else { + alreadyVisited.add(obj); + if (clazz.isArray()) { + visitArray(obj); + } else { + classSizeInfos.getUnchecked(clazz).visit(obj, this); + } + } +} + +private void visitArray(Object array) { + final Class componentType = array.getClass().getComponentType(); + final int length = Array.getLength(array); + if (componentType.isPrimitive()) { + increaseByArraySize(length, getPrimitiveFieldSize(componentType)); + } else { + increaseByArraySize(length, referenceSize); + // If we didn't use an ArrayElementsVisitor, we would be enqueueing every + // element of the array here instead. For large arrays, it would + // tremendously enlarge the queue. In essence, we're compressing it into + // a small command object instead. This is different than immediately + // visiting the elements, as their visiting is scheduled for the end of + // the current queue. + switch (length) { + case 0: { + break; + } + case 1: { + enqueue(Array.get(array, 0)); + break; + } + default: { + enqueue(new ArrayElementsVisitor((Object[]) array)); + } + } + } +} + +private void increaseByArraySize(int length, long elementSize) { + increaseSize(roundTo(arrayHeaderSize + length * elementSize, objectPadding)); +} + +private static class ArrayElementsVisitor { + private final Object[] array; + + ArrayElementsVisitor(Object[] array) { + this.array = array; + } + + public void visit(ObjectSizeCalculator calc) { + for (Object elem : array) { + if (elem != null) { + calc.visit(elem); + } + } + } +} + +void enqueue(Object obj) { + if (obj != null) { + pending.addLast(obj); + } +} + +void increaseSize(long objectSize) { + size += objectSize; +} + +@VisibleForTesting +static long roundTo(long x, int multiple) { + return ((x + multiple - 1) / multiple) * multiple; +} + +private class ClassSizeInfo { + // Padded fields + header size + private final long objectSize; + // Only the fields size - used to calculate the subclasses' memory + // footprint. + private final long fieldsSize; + private final Field[] referenceFields; + + public ClassSizeInfo(Class clazz) { + long fieldsSize = 0; + final List referenceFields = new LinkedList(); + for (Field f : clazz.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + final Class type = f.getType(); + if (type.isPrimitive()) { + fieldsSize += getPrimitiveFieldSize(type); + } else { + f.setAccessible(true); + referenceFields.add(f); + fieldsSize += referenceSize; + } + } + final Class superClass = clazz.getSuperclass(); + if (superClass != null) { + final ClassSizeInfo superClassInfo = classSizeInfos.getUnchecked(superClass); + fieldsSize += roundTo(superClassInfo.fieldsSize, superclassFieldPadding); + referenceFields.addAll(Arrays.asList(superClassInfo.referenceFields)); + } + this.fieldsSize = fieldsSize; + this.objectSize = roundTo(objectHeaderSize + fieldsSize, objectPadding); + this.referenceFields = referenceFields.toArray( + new Field[referenceFields.size()]); + } + + void visit(Object obj, ObjectSizeCalculator calc) { + calc.increaseSize(objectSize); + enqueueReferencedObjects(obj, calc); + } + + public void enqueueReferencedObjects(Object obj, ObjectSizeCalculator calc) { + for (Field f : referenceFields) { + try { + calc.enqueue(f.get(obj)); + } catch (IllegalAccessException e) { + final AssertionError ae = new AssertionError( + "Unexpected denial of access to " + f); + ae.initCause(e); + throw ae; + } + } + } +} + +private static long getPrimitiveFieldSize(Class type) { + if (type == boolean.class || type == byte.class) { + return 1; + } + if (type == char.class || type == short.class) { + return 2; + } + if (type == int.class || type == float.class) { + return 4; + } + if (type == long.class || type == double.class) { + return 8; + } + throw new AssertionError("Encountered unexpected primitive type " + + type.getName()); +} + +@VisibleForTesting +static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification() { + final String vmName = System.getProperty("java.vm.name"); + if (vmName == null || !(vmName.startsWith("Java HotSpot(TM) ") + || vmName.startsWith("OpenJDK") || vmName.startsWith("TwitterJDK"))) { + throw new UnsupportedOperationException( + "ObjectSizeCalculator only supported on HotSpot VM"); + } + + final String dataModel = System.getProperty("sun.arch.data.model"); + if ("32".equals(dataModel)) { + // Running with 32-bit data model + return new MemoryLayoutSpecification() { + @Override public int getArrayHeaderSize() { + return 12; + } + @Override public int getObjectHeaderSize() { + return 8; + } + @Override public int getObjectPadding() { + return 8; + } + @Override public int getReferenceSize() { + return 4; + } + @Override public int getSuperclassFieldPadding() { + return 4; + } + }; + } else if (!"64".equals(dataModel)) { + throw new UnsupportedOperationException("Unrecognized value '" + + dataModel + "' of sun.arch.data.model system property"); + } + + final String strVmVersion = System.getProperty("java.vm.version"); + final int vmVersion = Integer.parseInt(strVmVersion.substring(0, + strVmVersion.indexOf('.'))); + if (vmVersion >= 17) { + long maxMemory = 0; + for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { + maxMemory += mp.getUsage().getMax(); + } + if (maxMemory < 30L * 1024 * 1024 * 1024) { + // HotSpot 17.0 and above use compressed OOPs below 30GB of RAM total + // for all memory pools (yes, including code cache). + return new MemoryLayoutSpecification() { + @Override public int getArrayHeaderSize() { + return 16; + } + @Override public int getObjectHeaderSize() { + return 12; + } + @Override public int getObjectPadding() { + return 8; + } + @Override public int getReferenceSize() { + return 4; + } + @Override public int getSuperclassFieldPadding() { + return 4; + } + }; + } + } + + // In other cases, it's a 64-bit uncompressed OOPs object model + return new MemoryLayoutSpecification() { + @Override public int getArrayHeaderSize() { + return 24; + } + @Override public int getObjectHeaderSize() { + return 16; + } + @Override public int getObjectPadding() { + return 8; + } + @Override public int getReferenceSize() { + return 8; + } + @Override public int getSuperclassFieldPadding() { + return 8; + } + }; +} +}