001package org.cache2k.impl; 002 003/* 004 * #%L 005 * cache2k core package 006 * %% 007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import java.lang.reflect.Array; 026import java.lang.reflect.Field; 027import java.lang.reflect.Modifier; 028import java.util.ArrayList; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.List; 033import java.util.Set; 034 035/** 036 * Helper to estimate the size of object within a cache. Usage: Insert all objects to 037 * analyze with insert(), than call descend() to descend one level of the object graph. 038 * The end is reached when hasNext() yields false. The The result can be obtained via 039 * getBytes(). 040 * 041 * <p>The basic problem when we want to estimate the size is that it is never known 042 * whether an object is exclusive for our cache structure, or it is also referenced by 043 * some other classes. To do a "perfect" size measurement the whole java heap must be 044 * analyzed and it must be decided which object should be counted where. So, anyway, 045 * let's try a good guess, and see whether it is of some use. 046 * </p> 047 * 048 * <p>Heuristics: Top-Level objects are always counted at least once by instance. An identical 049 * object that is found via different ways when descending the graph is not counted at all. 050 * Object reached via a non-counted object are not counted.</p> 051 * 052 * <p>Caveats: If only one object contains a reference to a bigger static data structure 053 * that belongs to the system, the size could be totally wrong. Countermeasures: 054 * we do measure the size only if more then x objects are in the cache and we limit the 055 * depth. If objects have a reference e.g. to some system objects, or maybe the cache, these 056 * objects will not be counted, if the reference is appearing more than once. So an 057 * problem may still arise if there is one object within the cache that holds such 058 * a reference and the others not. Current countermeasures for this: Estimate size only 059 * when sufficient objects are in the cache; limit the descending depth.</p> 060 * 061 * @author Jens Wilke; created: 2013-06-25 062 */ 063@SuppressWarnings("unused") 064public class DepthSearchAndSizeCounter { 065 066 int bytes; 067 int objectCount; 068 int counter; 069 070 HashMap<Integer, SeenEntry> seen = new HashMap<Integer, SeenEntry>(); 071 Set<SeenEntry> next = new HashSet<SeenEntry>(); 072 073 /** 074 * Array of hash entries seen twice or more times. Top level objects will always be 075 * counted once. Secondary entries, will never be counted, since we cannot know 076 * if they are exclusive to the hash or not. 077 * */ 078 Set<SeenEntry> eleminate = new HashSet<SeenEntry>(); 079 080 boolean commonObjects = false; 081 boolean circles = false; 082 083 public void insert(Object o) { 084 counter++; 085 nextLevel(null, o); 086 } 087 088 public int getNextCount() { 089 return next.size(); 090 } 091 092 public boolean hasNext() { 093 return !next.isEmpty(); 094 } 095 096 public void descend() throws EstimationException { 097 SeenEntry e = null; 098 try { 099 Iterator<SeenEntry> it = next.iterator(); 100 next = new HashSet<SeenEntry>(); 101 eleminate = new HashSet<SeenEntry>(); 102 while (it.hasNext()) { 103 descend(e = it.next()); 104 } 105 } catch (Exception ex) { 106 List<Class<?>> _path = new ArrayList<Class<?>>(); 107 while (e != null) { 108 _path.add(0, e.object.getClass()); 109 e = e.via; 110 } 111 throw new EstimationException(ex, _path); 112 } 113 eleminateSeenEntries(); 114 } 115 116 /** 117 * Reset our counters but keep the lookup set of the objects seen so far. 118 */ 119 public void resetCounter() { 120 objectCount = 0; 121 bytes = 0; 122 counter = 0; 123 } 124 125 /** go down the subtree and make sure that we don't descend it further */ 126 void eleminateDescendantsFromNext(SeenEntry e) { 127 next.remove(e); 128 for (SeenEntry e2 : e.transitive) { 129 eleminateDescendantsFromNext(e2); 130 } 131 } 132 133 void eleminateSeenEntries() { 134 for (SeenEntry e : eleminate) { 135 eleminateDescendantsFromNext(e); 136 bytes -= e.getTotalBytes(); 137 objectCount -= e.getObjectCount(); 138 } 139 } 140 141 void descend(SeenEntry e) throws IllegalAccessException { 142 Object o = e.object; 143 144 Class<?> c = o.getClass(); 145 if (c.isArray()) { 146 recurseArray(e, c, o); 147 } else { 148 recurseFields(e, c, o); 149 } 150 } 151 152 void recurseArray(SeenEntry e, Class<?> c, Object o) throws IllegalAccessException { 153 Class<?> t = c.getComponentType(); 154 if (t.isPrimitive()) { 155 if (t == long.class || t == double.class) { 156 e.bytes += 8 * Array.getLength(o); 157 } else { 158 e.bytes += 4 * Array.getLength(o); 159 } 160 } else { 161 Object[] oa = (Object[]) o; 162 e.bytes += 8 * Array.getLength(o); 163 for (Object oi : oa) { 164 nextLevel(e, oi); 165 } 166 } 167 e.bytes += 24; // array overhead 168 bytes += e.bytes; 169 objectCount++; 170 } 171 172 void recurseFields(SeenEntry e, Class<?> c, Object o) throws IllegalAccessException { 173 if (c == null) { return; } 174 recurseFields(e, c.getSuperclass(), o); 175 Field[] fa = c.getDeclaredFields(); 176 for (Field f : fa) { 177 if (Modifier.isStatic(f.getModifiers())) { 178 continue; 179 } 180 Class<?> t = f.getType(); 181 if (t.isPrimitive()) { 182 if (t == long.class || t == double.class) { 183 e.bytes += 8; 184 } else { 185 e.bytes += 4; 186 } 187 continue; 188 } 189 e.bytes += 8; 190 f.setAccessible(true); 191 Object o2 = f.get(o); 192 nextLevel(e, o2); 193 } 194 e.bytes += 20; // object overhead 195 objectCount++; 196 bytes += e.bytes; 197 } 198 199 void nextLevel(SeenEntry _parent, Object o) { 200 if (o == null) { 201 return; 202 } 203 SeenEntry e = seen.get(System.identityHashCode(o)); 204 if (e != null) { 205 if (_parent != null) { checkObjectNavigationPath(_parent, e); } 206 e.referenceCount++; 207 if (!e.eleminated) { 208 eleminate.add(e); 209 e.eleminated = true; 210 } 211 } else { 212 e = new SeenEntry(o); 213 if (_parent != null) { 214 _parent.transitive.add(e); 215 e.via = _parent; 216 } 217 seen.put(System.identityHashCode(o), e); 218 next.add(e); 219 } 220 } 221 222 /** 223 * to know the accuracy we check the navigation path that has lead us to an object. 224 */ 225 final void checkObjectNavigationPath(SeenEntry _parent, SeenEntry e) { 226 if (!circles || !commonObjects) { 227 SeenEntry r0 = _parent.getRoot(); 228 SeenEntry r1 = e.getRoot(); 229 if (r0 == r1) { 230 circles = true; 231 } else { 232 commonObjects = true; 233 } 234 } 235 } 236 237 public int getByteCount() { 238 return bytes; 239 } 240 241 public int getObjectCount() { 242 return objectCount; 243 } 244 245 public int getCounter() { 246 return counter; 247 } 248 249 public boolean hasCommonObjects() { 250 return commonObjects; 251 } 252 253 public boolean hasCircles() { 254 return circles; 255 } 256 257 static class SeenEntry { 258 259 SeenEntry via; 260 Set<SeenEntry> transitive = new HashSet<SeenEntry>(); 261 Object object; 262 int referenceCount = 1; 263 int bytes; 264 boolean eleminated; 265 266 SeenEntry(Object object) { 267 this.object = object; 268 } 269 270 public final SeenEntry getRoot() { 271 return via == null ? this : via.getRoot(); 272 } 273 274 public int getTotalBytes() { 275 int v = bytes; 276 for (SeenEntry e : transitive) { 277 if (!e.eleminated) { 278 v += e.getTotalBytes(); 279 } 280 } 281 return v; 282 } 283 284 /** Count of object seen below this one, including itself */ 285 public int getObjectCount() { 286 int v = 1; 287 for (SeenEntry e : transitive) { 288 if (!e.eleminated && e.bytes > 0) { 289 v++; 290 v += e.getObjectCount(); 291 } 292 } 293 return v; 294 } 295 296 } 297 298 public static class EstimationException extends Exception { 299 300 List<Class<?>> path; 301 302 EstimationException(Throwable cause, List<Class<?>> path) { 303 super(cause); 304 this.path = path; 305 } 306 307 public List<Class<?>> getPath() { 308 return path; 309 } 310 } 311 312}