1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.memleak; 34 35 import java.lang.reflect.Modifier; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 import org.openjdk.jmc.common.IMCOldObject; 45 import org.openjdk.jmc.common.IMCOldObjectArray; 46 import org.openjdk.jmc.common.IMCOldObjectField; 47 import org.openjdk.jmc.common.IMCOldObjectGcRoot; 48 import org.openjdk.jmc.common.IMCType; 49 import org.openjdk.jmc.common.item.IItem; 50 import org.openjdk.jmc.common.unit.IQuantity; 51 52 /** 53 * A data type representing alive objects in a Java heap used in a {@link ReferenceTreeModel}. 54 */ 55 public class ReferenceTreeObject implements IMCOldObject { 56 57 public enum ReferenceTreeObjectType { 58 Array, InstanceField, JavaObject, LeakObject 59 } 60 61 private final List<ReferenceTreeObject> children = new ArrayList<>(); 62 private final Set<IItem> items = new HashSet<>(); 63 private final Map<IQuantity, ReferenceTreeObject> leafNodes = new HashMap<>(); 64 65 private String rootDescription; 66 private IMCOldObject object; 67 private int objectsKeptAliveCount; 68 private ReferenceTreeObject parent; 69 private IQuantity timestamp; 70 private double leakRelevance; 71 private int distanceFromRoot; 72 public static final int FORMAT_PACKAGE = 0b00001; 73 public static final int FORMAT_FIELD = 0b00010; 74 public static final int FORMAT_STATIC_MODIFIER = 0b00100; 75 public static final int FORMAT_OTHER_MODIFIERS = 0b01000; 76 public static final int FORMAT_ARRAY_INFO = 0b10000; 77 78 /** 79 * @param timestamp 80 * a timestamp representing when the object was allocated 81 * @param object 82 * the object itself 83 */ 84 ReferenceTreeObject(IQuantity timestamp, IMCOldObject object) { 85 this.timestamp = timestamp; 86 this.object = object; 87 leakRelevance = -1; 88 distanceFromRoot = 0; 89 } 90 91 /** 92 * @param distance 93 * the distance from the root object 94 */ 95 void setDistanceFromRoot(int distance) { 96 this.distanceFromRoot = distance; 97 } 98 99 /** 100 * @return the number of steps in a referral chain from the root object 101 */ 102 public int getDistanceFromRoot() { 103 return distanceFromRoot; 104 } 105 106 /** 107 * @param relevance 108 * how relevant this object is as a leak candidate 109 */ 110 void setLeakRelevance(double relevance) { 111 this.leakRelevance = relevance; 112 } 113 114 /** 115 * @return the relevance of this object for memory leak detection 116 */ 117 public double getLeakRelevance() { 118 return this.leakRelevance; 119 } 120 121 /** 122 * @param node 123 * a child to be added to this object 124 */ 125 void addChild(ReferenceTreeObject node) { 126 if (!children.contains(node)) { 127 children.add(node); 128 node.setParent(this); 129 } 130 } 131 132 /** 133 * @param item 134 * an item this object keeps alive 135 */ 136 void addItem(IItem item) { 137 items.add(item); 138 } 139 140 /** 141 * This method is used when it is necessary to get information about all objects this object 142 * keeps alive in the {@link ReferenceTreeModel}. E.g. the Mission Control GUI uses this when a 143 * user selects a row in the tree to show everything below it as well in the properties view. 144 * 145 * @return a set representing all {@link IItem} objects this object keeps alive, including 146 * itself 147 */ 148 public Set<IItem> getItems() { 149 return items; 150 } 151 152 /** 153 * @param root 154 * a GC root description 155 */ 156 void addRoot(IMCOldObjectGcRoot root) { 157 if (root != null) { 158 rootDescription = root.toString(); 159 } 160 } 161 162 /** 163 * @return the GC root description 164 */ 165 public String getRootDescription() { 166 return rootDescription; 167 } 168 169 @Override 170 public IQuantity getAddress() { 171 return object.getAddress(); 172 } 173 174 @Override 175 public IMCOldObjectArray getReferrerArray() { 176 return object.getReferrerArray(); 177 } 178 179 /** 180 * @return if this object is an array, gets information representing that array, {@code null} 181 * otherwise 182 */ 183 public IMCOldObjectArray getArray() { 184 if (getChildren().size() > 0) { 185 return getChildren().get(0).getReferrerArray(); 186 } 187 return null; 188 } 189 190 /** 191 * @return the children of this object 192 */ 193 public List<ReferenceTreeObject> getChildren() { 194 return children; 195 } 196 197 @Override 198 public IMCOldObjectField getReferrerField() { 199 return object.getReferrerField(); 200 } 201 202 /** 203 * @return if this object is a field, gets information representing that field, {@code null} 204 * otherwise 205 */ 206 public IMCOldObjectField getField() { 207 if (getChildren().size() > 0) { 208 return getChildren().get(0).getReferrerField(); 209 } 210 return null; 211 } 212 213 @Override 214 public String getDescription() { 215 return object.getDescription(); 216 } 217 218 /** 219 * @return the number of objects this object keeps alive 220 */ 221 public int getObjectsKeptAliveCount() { 222 return objectsKeptAliveCount; 223 } 224 225 /** 226 * This method returns an object that is keeping this object alive. 227 * 228 * @return the object linking to this object from the direction of the gc root 229 */ 230 public ReferenceTreeObject getParent() { 231 return parent; 232 } 233 234 @Override 235 public IMCOldObject getReferrer() { 236 return object.getReferrer(); 237 } 238 239 /** 240 * @return the timestamp this object was allocated 241 */ 242 public IQuantity getTimestamp() { 243 return timestamp; 244 } 245 246 @Override 247 public IMCType getType() { 248 return object.getType(); 249 } 250 251 @Override 252 public int hashCode() { 253 return object.hashCode(); 254 } 255 256 @Override 257 public boolean equals(Object o) { 258 if (o instanceof ReferenceTreeObject) { 259 ReferenceTreeObject that = (ReferenceTreeObject) o; 260 return that.getAddress().equals(this.getAddress()); 261 } 262 return false; 263 } 264 265 /** 266 * Increments the number of objects this object keeps alive. 267 */ 268 void incrementObjectsKeptAliveCount() { 269 objectsKeptAliveCount++; 270 } 271 272 /** 273 * @param parent 274 * the parent of this object 275 */ 276 public void setParent(ReferenceTreeObject parent) { 277 this.parent = parent; 278 } 279 280 /** 281 * @return the Root object 282 */ 283 public ReferenceTreeObject getRootObject() { 284 if (this.parent == null) { 285 return this; 286 } else { 287 ReferenceTreeObject rto = this.parent; 288 while (rto.getParent() != null) { 289 rto = rto.getParent(); 290 } 291 return rto; 292 } 293 } 294 295 @Override 296 public int getReferrerSkip() { 297 return object.getReferrerSkip(); 298 } 299 300 /** 301 * @return Map containing allocation time and its leafnode object 302 */ 303 public Map<IQuantity, ReferenceTreeObject> getLeafNodes() { 304 return Collections.unmodifiableMap(leafNodes); 305 } 306 307 /** 308 * @param leafNode 309 * Object which is leaked 310 */ 311 public void updateLeafNode(ReferenceTreeObject leafNode) { 312 leafNodes.put(leafNode.getTimestamp(), leafNode); 313 } 314 315 /** 316 * Returns a string representation of this object. 317 * 318 * @param displayFormatting 319 * an int describing how this object is to be represented, using bitwise masking of 320 * constants defined in {@link ReferenceTreeObject} 321 * @return a human readable string representation of this object 322 */ 323 public String toString(int displayFormatting) { 324 String text = getType().getTypeName(); 325 if ((displayFormatting & ReferenceTreeObject.FORMAT_PACKAGE) != 0) { 326 text = getType().getFullName(); 327 } 328 if (this.getChildren().size() > 0 && getField() != null) { 329 if ((displayFormatting & ReferenceTreeObject.FORMAT_FIELD) != 0) { 330 text = text + "." + getField().getName(); //$NON-NLS-1$ 331 } 332 if (getField().getModifier() != null) { 333 String modifiers = Modifier.toString(getField().getModifier()); 334 if ((displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) != 0 335 && (displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) == 0) { 336 if (modifiers.contains("static")) { //$NON-NLS-1$ 337 text = "static " + text; //$NON-NLS-1$ 338 } 339 } else if ((displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) != 0 340 && (displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) == 0) { 341 String nonStaticModifiers = modifiers.replaceAll("static ", ""); //$NON-NLS-1$ //$NON-NLS-2$ 342 if (!"".equals(nonStaticModifiers)) { //$NON-NLS-1$ 343 text = nonStaticModifiers + " " + text; //$NON-NLS-1$ 344 } 345 } else if ((displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) != 0 346 && (displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) != 0) { 347 if (!"".equals(modifiers)) { //$NON-NLS-1$ 348 text = modifiers + " " + text; //$NON-NLS-1$ 349 } 350 } 351 } 352 } 353 if ((displayFormatting & ReferenceTreeObject.FORMAT_ARRAY_INFO) != 0 && this.getArray() != null) { 354 if (text.endsWith("[]")) { //$NON-NLS-1$ 355 text = text.substring(0, text.length() - 1) + getArray().getIndex() + "/" + getArray().getSize() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ 356 } else { 357 text = text + getArray().getIndex() + "/" + getArray().getSize(); //$NON-NLS-1$ 358 } 359 } 360 return text.trim(); 361 } 362 363 }