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.HashSet;
  38 import java.util.List;
  39 import java.util.Set;
  40 
  41 import org.openjdk.jmc.common.IMCOldObject;
  42 import org.openjdk.jmc.common.IMCOldObjectArray;
  43 import org.openjdk.jmc.common.IMCOldObjectField;
  44 import org.openjdk.jmc.common.IMCOldObjectGcRoot;
  45 import org.openjdk.jmc.common.IMCType;
  46 import org.openjdk.jmc.common.item.IItem;
  47 import org.openjdk.jmc.common.unit.IQuantity;
  48 
  49 /**
  50  * A data type representing alive objects in a Java heap used in a {@link ReferenceTreeModel}.
  51  */
  52 public class ReferenceTreeObject implements IMCOldObject {
  53 
  54         public enum ReferenceTreeObjectType {
  55                 Array, InstanceField, JavaObject, LeakObject
  56         }
  57 
  58         private final List<ReferenceTreeObject> children = new ArrayList<>();
  59         private final Set<IItem> items = new HashSet<>();
  60 
  61         private String rootDescription;
  62         private IMCOldObject object;
  63         private int objectsKeptAliveCount;
  64         private ReferenceTreeObject parent;
  65         private IQuantity timestamp;
  66         private double leakRelevance;
  67         private int distanceFromRoot;
  68         public static final int FORMAT_PACKAGE = 0b00001;
  69         public static final int FORMAT_FIELD = 0b00010;
  70         public static final int FORMAT_STATIC_MODIFIER = 0b00100;
  71         public static final int FORMAT_OTHER_MODIFIERS = 0b01000;
  72         public static final int FORMAT_ARRAY_INFO = 0b10000;
  73 
  74         /**
  75          * @param timestamp
  76          *            a timestamp representing when the object was allocated
  77          * @param object
  78          *            the object itself
  79          */
  80         ReferenceTreeObject(IQuantity timestamp, IMCOldObject object) {
  81                 this.timestamp = timestamp;
  82                 this.object = object;
  83                 leakRelevance = -1;
  84                 distanceFromRoot = 0;
  85         }
  86 
  87         /**
  88          * @param distance
  89          *            the distance from the root object
  90          */
  91         void setDistanceFromRoot(int distance) {
  92                 this.distanceFromRoot = distance;
  93         }
  94 
  95         /**
  96          * @return the number of steps in a referral chain from the root object
  97          */
  98         public int getDistanceFromRoot() {
  99                 return distanceFromRoot;
 100         }
 101 
 102         /**
 103          * @param relevance
 104          *            how relevant this object is as a leak candidate
 105          */
 106         void setLeakRelevance(double relevance) {
 107                 this.leakRelevance = relevance;
 108         }
 109 
 110         /**
 111          * @return the relevance of this object for memory leak detection
 112          */
 113         public double getLeakRelevance() {
 114                 return this.leakRelevance;
 115         }
 116 
 117         /**
 118          * @param node
 119          *            a child to be added to this object
 120          */
 121         void addChild(ReferenceTreeObject node) {
 122                 if (!children.contains(node)) {
 123                         children.add(node);
 124                         node.setParent(this);
 125                 }
 126         }
 127 
 128         /**
 129          * @param item
 130          *            an item this object keeps alive
 131          */
 132         void addItem(IItem item) {
 133                 items.add(item);
 134         }
 135 
 136         /**
 137          * This method is used when it is necessary to get information about all objects this object
 138          * keeps alive in the {@link ReferenceTreeModel}. E.g. the Mission Control GUI uses this when a
 139          * user selects a row in the tree to show everything below it as well in the properties view.
 140          *
 141          * @return a set representing all {@link IItem} objects this object keeps alive, including
 142          *         itself
 143          */
 144         public Set<IItem> getItems() {
 145                 return items;
 146         }
 147 
 148         /**
 149          * @param root
 150          *            a GC root description
 151          */
 152         void addRoot(IMCOldObjectGcRoot root) {
 153                 if (root != null) {
 154                         rootDescription = root.toString();
 155                 }
 156         }
 157 
 158         /**
 159          * @return the GC root description
 160          */
 161         public String getRootDescription() {
 162                 return rootDescription;
 163         }
 164 
 165         @Override
 166         public IQuantity getAddress() {
 167                 return object.getAddress();
 168         }
 169 
 170         @Override
 171         public IMCOldObjectArray getReferrerArray() {
 172                 return object.getReferrerArray();
 173         }
 174 
 175         /**
 176          * @return if this object is an array, gets information representing that array, {@code null}
 177          *         otherwise
 178          */
 179         public IMCOldObjectArray getArray() {
 180                 if (getChildren().size() > 0) {
 181                         return getChildren().get(0).getReferrerArray();
 182                 }
 183                 return null;
 184         }
 185 
 186         /**
 187          * @return the children of this object
 188          */
 189         public List<ReferenceTreeObject> getChildren() {
 190                 return children;
 191         }
 192 
 193         @Override
 194         public IMCOldObjectField getReferrerField() {
 195                 return object.getReferrerField();
 196         }
 197 
 198         /**
 199          * @return if this object is a field, gets information representing that field, {@code null}
 200          *         otherwise
 201          */
 202         public IMCOldObjectField getField() {
 203                 if (getChildren().size() > 0) {
 204                         return getChildren().get(0).getReferrerField();
 205                 }
 206                 return null;
 207         }
 208 
 209         @Override
 210         public String getDescription() {
 211                 return object.getDescription();
 212         }
 213 
 214         /**
 215          * @return the number of objects this object keeps alive
 216          */
 217         public int getObjectsKeptAliveCount() {
 218                 return objectsKeptAliveCount;
 219         }
 220 
 221         /**
 222          * This method returns an object that is keeping this object alive.
 223          *
 224          * @return the object linking to this object from the direction of the gc root
 225          */
 226         public ReferenceTreeObject getParent() {
 227                 return parent;
 228         }
 229 
 230         @Override
 231         public IMCOldObject getReferrer() {
 232                 return object.getReferrer();
 233         }
 234 
 235         /**
 236          * @return the timestamp this object was allocated
 237          */
 238         public IQuantity getTimestamp() {
 239                 return timestamp;
 240         }
 241 
 242         @Override
 243         public IMCType getType() {
 244                 return object.getType();
 245         }
 246 
 247         @Override
 248         public int hashCode() {
 249                 return object.hashCode();
 250         }
 251 
 252         @Override
 253         public boolean equals(Object o) {
 254                 if (o instanceof ReferenceTreeObject) {
 255                         ReferenceTreeObject that = (ReferenceTreeObject) o;
 256                         return that.getAddress().equals(this.getAddress());
 257                 }
 258                 return false;
 259         }
 260 
 261         /**
 262          * Increments the number of objects this object keeps alive.
 263          */
 264         void incrementObjectsKeptAliveCount() {
 265                 objectsKeptAliveCount++;
 266         }
 267 
 268         /**
 269          * @param parent
 270          *            the parent of this object
 271          */
 272         public void setParent(ReferenceTreeObject parent) {
 273                 this.parent = parent;
 274         }
 275 
 276         @Override
 277         public int getReferrerSkip() {
 278                 return object.getReferrerSkip();
 279         }
 280 
 281         /**
 282          * Returns a string representation of this object.
 283          *
 284          * @param displayFormatting
 285          *            an int describing how this object is to be represented, using bitwise masking of
 286          *            constants defined in {@link ReferenceTreeObject}
 287          * @return a human readable string representation of this object
 288          */
 289         public String toString(int displayFormatting) {
 290                 String text = getType().getTypeName();
 291                 if ((displayFormatting & ReferenceTreeObject.FORMAT_PACKAGE) != 0) {
 292                         text = getType().getFullName();
 293                 }
 294                 if (this.getChildren().size() > 0 && getField() != null) {
 295                         if ((displayFormatting & ReferenceTreeObject.FORMAT_FIELD) != 0) {
 296                                 text = text + "." + getField().getName(); //$NON-NLS-1$
 297                         }
 298                         if (getField().getModifier() != null) {
 299                                 String modifiers = Modifier.toString(getField().getModifier());
 300                                 if ((displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) != 0
 301                                                 && (displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) == 0) {
 302                                         if (modifiers.contains("static")) { //$NON-NLS-1$
 303                                                 text = "static " + text; //$NON-NLS-1$
 304                                         }
 305                                 } else if ((displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) != 0
 306                                                 && (displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) == 0) {
 307                                         String nonStaticModifiers = modifiers.replaceAll("static ", ""); //$NON-NLS-1$ //$NON-NLS-2$
 308                                         if (!"".equals(nonStaticModifiers)) { //$NON-NLS-1$
 309                                                 text = nonStaticModifiers + " " + text; //$NON-NLS-1$
 310                                         }
 311                                 } else if ((displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) != 0
 312                                                 && (displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) != 0) {
 313                                         if (!"".equals(modifiers)) { //$NON-NLS-1$
 314                                                 text = modifiers + " " + text; //$NON-NLS-1$
 315                                         }
 316                                 }
 317                         }
 318                 }
 319                 if ((displayFormatting & ReferenceTreeObject.FORMAT_ARRAY_INFO) != 0 && this.getArray() != null) {
 320                         if (text.endsWith("[]")) { //$NON-NLS-1$
 321                                 text = text.substring(0, text.length() - 1) + getArray().getIndex() + "/" + getArray().getSize() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
 322                         } else {
 323                                 text = text + getArray().getIndex() + "/" + getArray().getSize(); //$NON-NLS-1$
 324                         }
 325                 }
 326                 return text.trim();
 327         }
 328 
 329 }