001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util; 017 018import java.lang.annotation.ElementType; 019import java.lang.annotation.Retention; 020import java.lang.annotation.RetentionPolicy; 021import java.lang.annotation.Target; 022import java.lang.reflect.Field; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.IdentityHashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.stream.Collectors; 031 032import org.apache.commons.lang3.tuple.Pair; 033import org.openjdk.jol.info.ClassData; 034import org.openjdk.jol.info.FieldData; 035import org.openjdk.jol.layouters.CurrentLayouter; 036import org.openjdk.jol.util.ObjectUtils; 037import org.slf4j.LoggerFactory; 038 039/** 040 * Helper for manipulating objects heap size. 041 */ 042public final class SizeUtils 043{ 044 private static Map<Class<?>, Pair<Long, List<Field>>> __cache = new ConcurrentHashMap<>(); 045 046 static 047 { 048 // add opens to JOL for all java.base packages, to avoid illegal access warnings 049 Object.class.getModule().getPackages().forEach(p -> 050 { 051 if (Object.class.getModule().isOpen(p, ObjectUtils.class.getModule())) 052 { 053 Object.class.getModule().addOpens(p, ObjectUtils.class.getModule()); 054 } 055 }); 056 } 057 058 private SizeUtils() 059 { 060 // empty 061 } 062 063 /** 064 * Calculates the heap size of the given object. 065 * @param object the object 066 * @return the size of the object. 067 */ 068 public static long sizeOf(Object object) 069 { 070 if (object == null) 071 { 072 return 0; 073 } 074 075 try 076 { 077 return _sizeOf(object, Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>())); 078 } 079 catch (Error e) 080 { 081 // Special case for intercepting eg. StackOverflowError and log the concerned object 082 LoggerFactory.getLogger(SizeUtils.class.getName()).error("Error computing size of {}", object, e); 083 throw e; 084 } 085 } 086 087 private static long _sizeOf(Object object, Set<Object> visited) 088 { 089 if (!visited.add(object)) 090 { 091 return 0; 092 } 093 094 Class<?> klass = object.getClass(); 095 096 if (klass.isArray()) 097 { 098 long size = new CurrentLayouter().layout(ClassData.parseInstance(object)).instanceSize(); 099 100 if (klass.getComponentType().isPrimitive()) 101 { 102 return size; 103 } 104 105 Object[] array = (Object[]) object; 106 107 return Arrays.stream(array) 108 .filter(SizeUtils::_isComputable) 109 .map(value -> _sizeOf(value, visited)) 110 .reduce(size, Long::sum); 111 } 112 113 // we cache the shallow class size and the list of class' fields 114 Pair<Long, List<Field>> data = __cache.computeIfAbsent(klass, c -> 115 { 116 ClassData classData = ClassData.parseClass(c); 117 118 List<Field> fields = classData.fields().stream() 119 .map(FieldData::refField) 120 .filter(field -> !field.getType().isPrimitive()) 121 .filter(field -> !field.isAnnotationPresent(ExcludeFromSizeCalculation.class)) 122 .collect(Collectors.toList()); 123 124 return Pair.of(new CurrentLayouter().layout(classData).instanceSize(), fields); 125 }); 126 127 // the actual size of a non-array object is the sum of its shallow size and the size of each of its fields 128 long size = data.getLeft(); 129 130 for (Field field : data.getRight()) 131 { 132 Object value = ObjectUtils.value(object, field); 133 134 if (_isComputable(value)) 135 { 136 size += _sizeOf(value, visited); 137 } 138 } 139 140 return size; 141 } 142 143 private static boolean _isComputable(Object object) 144 { 145 return object != null && !(object instanceof Class); 146 } 147 148 /** 149 * Fields annotated with {@link ExcludeFromSizeCalculation} are excluded for size calculation 150 */ 151 @Retention(RetentionPolicy.RUNTIME) 152 @Target(ElementType.FIELD) 153 public static @interface ExcludeFromSizeCalculation 154 { 155 // marker annotation 156 } 157}