Bug 573598 Display unindexed objects for dumps with discarded objects

Initial code drop. Use ObjectReference to look for unindexed objects.

Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=573598
Change-Id: Id78ae91db739dbd1d781a036c33233c3d11490dc
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/HashEntriesQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/HashEntriesQuery.java
index 01431be..5c48ace 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/HashEntriesQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/HashEntriesQuery.java
@@ -355,17 +355,30 @@
                     for (Map.Entry<IObject, IObject> me : map)

                     {

                         Entry e;

+                        int keyId,valueId;

+                        try

+                        {

+                            keyId = (me.getKey() != null) ? me.getKey().getObjectId() : -1;

+                        }

+                        catch (RuntimeException e1)

+                        {

+                            keyId = -1;

+                        }

+                        try

+                        {

+                            valueId = (me.getValue() != null) ? me.getValue().getObjectId() : -1;

+                        }

+                        catch (RuntimeException e1)

+                        {

+                            valueId = -1;

+                        }

                         if (me instanceof IObject)

                         {

                             IObject meObject = (IObject) me;

-                            int keyId = (me.getKey() != null) ? me.getKey().getObjectId() : -1;

-                            int valueId = (me.getValue() != null) ? me.getValue().getObjectId() : -1;

                             e = new Entry(obj.getObjectId(), obj.getDisplayName(), keyId, valueId);

                         }

                         else

                         {

-                            int keyId = (me.getKey() != null) ? me.getKey().getObjectId() : -1;

-                            int valueId = (me.getValue() != null) ? me.getValue().getObjectId() : -1;

                             e = new Entry(objectId, obj.getDisplayName(), keyId, valueId);

                         }

                         hashEntries1.add(e);

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/MapCollisionRatioQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/MapCollisionRatioQuery.java
index 83985d7..ce532c4 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/MapCollisionRatioQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/MapCollisionRatioQuery.java
@@ -16,6 +16,7 @@
 

 import java.util.Arrays;

 

+import org.eclipse.mat.SnapshotException;

 import org.eclipse.mat.collect.HashMapIntLong;

 import org.eclipse.mat.collect.HashMapIntObject;

 import org.eclipse.mat.inspections.collectionextract.CollectionExtractionUtils;

@@ -167,7 +168,7 @@
                         }

                     }

                 }

-                catch (RuntimeException e)

+                catch (RuntimeException | SnapshotException e)

                 {

                     int classId = obj.getClazz().getObjectId();

                     if (!exceptions.containsKey(classId))

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/PrimitiveArraysWithAConstantValueQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/PrimitiveArraysWithAConstantValueQuery.java
index 2d2faf0..dc59b05 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/PrimitiveArraysWithAConstantValueQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/collections/PrimitiveArraysWithAConstantValueQuery.java
@@ -14,6 +14,7 @@
  *******************************************************************************/

 package org.eclipse.mat.inspections.collections;

 

+import java.lang.reflect.Array;

 import java.util.Arrays;

 

 import org.eclipse.mat.collect.HashMapIntObject;

@@ -118,17 +119,36 @@
                 int length = array.getLength();

                 if (length > 1)

                 {

+                    // Read in chunks as DTFJ is slow for single reads

+                    final int BUFSIZE = 16 * 1024;

                     boolean allSame = true;

-                    Object value0 = array.getValueAt(0);

-                    for (int i = 1; i < length; i++)

+                    Object value0 = null;

+                    for (int i = 0; allSame && i < length;)

                     {

-                        Object valueAt = array.getValueAt(i);

-                        if (valueAt.equals(value0))

-                            continue;

+                        int buflen = Math.min(length - i, BUFSIZE);

+                        Object o = array.getValueArray(i, buflen);

+                        int j;

+                        if (i == 0)

+                        {

+                            value0 = Array.get(objectIds, 0);

+                            j = 1;

+                        }

                         else

                         {

-                            allSame = false;

-                            break;

+                            j = 0;

+                        }

+                        int actlen = Array.getLength(o);

+                        i += actlen;

+                        for (;j < actlen; ++j)

+                        {

+                            Object valueAt = Array.get(o, j);

+                            if (valueAt.equals(value0))

+                                continue;

+                            else

+                            {

+                                allSame = false;

+                                break;

+                            }

                         }

                     }

                     if (allSame)

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/MapCollectionExtractorBase.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/MapCollectionExtractorBase.java
index 1728258..90ad26c 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/MapCollectionExtractorBase.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/collectionextract/MapCollectionExtractorBase.java
@@ -1,130 +1,141 @@
-/*******************************************************************************
- * Copyright (c) 2008, 2019 SAP AG, IBM Corporation and others
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- *    SAP AG - initial API and implementation
- *    IBM Corporation - enhancements and fixes
- *    James Livingston - expose collection utils as API
- *******************************************************************************/
-package org.eclipse.mat.internal.collectionextract;
-
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-import org.eclipse.mat.SnapshotException;
-import org.eclipse.mat.inspections.collectionextract.IMapExtractor;
-import org.eclipse.mat.snapshot.ISnapshot;
-import org.eclipse.mat.snapshot.model.IObject;
-import org.eclipse.mat.snapshot.model.IObjectArray;
-
-public abstract class MapCollectionExtractorBase implements IMapExtractor
-{
-    protected final String keyField;
-    protected final String valueField;
-
-    public MapCollectionExtractorBase(String keyField, String valueField)
-    {
-        this.keyField = keyField;
-        this.valueField = valueField;
-    }
-
-    public boolean hasCapacity()
-    {
-        return hasExtractableArray();
-    }
-
-    public Integer getCapacity(IObject coll) throws SnapshotException
-    {
-        IObjectArray table = extractEntries(coll);
-        return (table == null) ? null : table.getLength();
-    }
-
-    public Iterator<Entry<IObject, IObject>> extractMapEntries(IObject coll)
-    {
-        try
-        {
-            return new MapEntryIterator(coll.getSnapshot(), coll, extractEntryIds(coll));
-        }
-        catch (SnapshotException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private class MapEntryIterator implements Iterator<Entry<IObject, IObject>>
-    {
-        private final ISnapshot snapshot;
-        private final int[] ids;
-        private int idx;
-        private IObject coll;
-
-        public MapEntryIterator(ISnapshot snapshot, IObject coll, int[] ids)
-        {
-            this.snapshot = snapshot;
-            this.ids = ids;
-            this.idx = 0;
-            this.coll = coll;
-        }
-
-        public boolean hasNext()
-        {
-            return idx < ids.length;
-        }
-
-        public Entry<IObject, IObject> next()
-        {
-            try
-            {
-                IObject obj = snapshot.getObject(ids[idx++]);
-                return new EntryObject(obj, getEntryKey(obj), getEntryValue(obj));
-            }
-            catch (SnapshotException e)
-            {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void remove()
-        {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    protected IObject getEntryKey(IObject obj)
-    {
-        try
-        {
-            IObject ret = (IObject) obj.resolveValue(keyField);
-            if (ret != null)
-            {
-                return ret;
-            }
-            else
-            {
-                return null;
-            }
-        }
-        catch (SnapshotException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    protected IObject getEntryValue(IObject obj)
-    {
-        try
-        {
-            return (IObject) obj.resolveValue(valueField);
-        }
-        catch (SnapshotException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-}
+/*******************************************************************************

+ * Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License 2.0

+ * which accompanies this distribution, and is available at

+ * https://www.eclipse.org/legal/epl-2.0/

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ * Contributors:

+ *    SAP AG - initial API and implementation

+ *    IBM Corporation - enhancements and fixes

+ *    James Livingston - expose collection utils as API

+ *******************************************************************************/

+package org.eclipse.mat.internal.collectionextract;

+

+import java.util.Iterator;

+import java.util.Map.Entry;

+

+import org.eclipse.mat.SnapshotException;

+import org.eclipse.mat.inspections.collectionextract.IMapExtractor;

+import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.model.IObject;

+import org.eclipse.mat.snapshot.model.IObjectArray;

+

+public abstract class MapCollectionExtractorBase implements IMapExtractor

+{

+    protected final String keyField;

+    protected final String valueField;

+

+    public MapCollectionExtractorBase(String keyField, String valueField)

+    {

+        this.keyField = keyField;

+        this.valueField = valueField;

+    }

+

+    public boolean hasCapacity()

+    {

+        return hasExtractableArray();

+    }

+

+    public Integer getCapacity(IObject coll) throws SnapshotException

+    {

+        IObjectArray table = extractEntries(coll);

+        return (table == null) ? null : table.getLength();

+    }

+

+    public Iterator<Entry<IObject, IObject>> extractMapEntries(IObject coll)

+    {

+        try

+        {

+            return new MapEntryIterator(coll.getSnapshot(), coll, extractEntryIds(coll));

+        }

+        catch (SnapshotException e)

+        {

+            throw new RuntimeException(e);

+        }

+    }

+

+    private class MapEntryIterator implements Iterator<Entry<IObject, IObject>>

+    {

+        private final ISnapshot snapshot;

+        private final int[] ids;

+        private int idx;

+        private IObject coll;

+

+        public MapEntryIterator(ISnapshot snapshot, IObject coll, int[] ids)

+        {

+            this.snapshot = snapshot;

+            this.ids = ids;

+            this.idx = 0;

+            this.coll = coll;

+        }

+

+        public boolean hasNext()

+        {

+            return idx < ids.length;

+        }

+

+        public Entry<IObject, IObject> next()

+        {

+            try

+            {

+                IObject obj = snapshot.getObject(ids[idx++]);

+                return new EntryObject(obj, null, null) {

+                    public IObject getKey()

+                    {

+                        return getEntryKey(obj);

+                    }

+                    public IObject getValue()

+                    {

+                        return getEntryValue(obj);

+                    }

+                };

+            }

+            catch (SnapshotException e)

+            {

+                throw new RuntimeException(e);

+            }

+        }

+

+        public void remove()

+        {

+            throw new UnsupportedOperationException();

+        }

+    }

+

+    protected IObject getEntryKey(IObject obj)

+    {

+        try

+        {

+            IObject ret = (IObject) obj.resolveValue(keyField);

+            if (ret != null)

+            {

+                return ret;

+            }

+            else

+            {

+                return null;

+            }

+        }

+        catch (SnapshotException e)

+        {

+            //throw new RuntimeException(e);

+            return null;

+        }

+    }

+

+    protected IObject getEntryValue(IObject obj)

+    {

+        try

+        {

+            return (IObject) obj.resolveValue(valueField);

+        }

+        catch (SnapshotException e)

+        {

+            //throw new RuntimeException(e);

+            return null;

+        }

+    }

+}

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/Field.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/Field.java
index bec5864..baa6094 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/Field.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/Field.java
@@ -1,10 +1,10 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2018 SAP AG and others.

+ * Copyright (c) 2008, 2021 SAP AG and others.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

- * https://www.eclipse.org/legal/epl-2.0/
- *
+ * https://www.eclipse.org/legal/epl-2.0/

+ *

  * SPDX-License-Identifier: EPL-2.0

  *

  * Contributors:

@@ -29,16 +29,18 @@
      * @param name the name of the field

      * @param type the type {@link IObject.Type}

      * @param value

-     * value is one of 

-     * ObjectReference - for an object field

-     * Byte - for a byte field

-     * Short - for a short field

-     * Integer - for an int field

-     * Long - for a long field

-     * Boolean - for a boolean field

-     * Char - for a char field

-     * Float - for a float field

-     * Double - for a double field

+     * value is one of

+     * <dl>

+     * <dt>{@link ObjectReference}</dt><dd>for an object field</dd>

+     * <dt>{@link Byte}</dt><dd>for a byte field</dd>

+     * <dt>{@link Short}</dt><dd>for a short field</dd>

+     * <dt>{@link Integer}</dt><dd>for an int field</dd>

+     * <dt>{@link Long}</dt><dd>for a long field</dd>

+     * <dt>{@link Boolean}</dt><dd>for a boolean field</dd>

+     * <dt>{@link Character}</dt><dd>for a char field</dd>

+     * <dt>{@link Float}</dt><dd>for a float field</dd>

+     * <dt>{@link Double}</dt><dd>for a double field</dd>

+     * </dl>

      */

     public Field(String name, int type, Object value)

     {

@@ -49,15 +51,17 @@
     /**

      * Gets the value of the field.

      * @return

-     * ObjectReference - for an object field

-     * Byte - for a byte field

-     * Short - for a short field

-     * Integer - for an int field

-     * Long - for a long field

-     * Boolean - for a boolean field

-     * Char - for a char field

-     * Float - for a float field

-     * Double - for a double field

+     * <dl>

+     * <dt>{@link ObjectReference}</dt><dd>for an object field</dd>

+     * <dt>{@link Byte}</dt><dd>for a byte field</dd>

+     * <dt>{@link Short}</dt><dd>for a short field</dd>

+     * <dt>{@link Integer}</dt><dd>for an int field</dd>

+     * <dt>{@link Long}</dt><dd>for a long field</dd>

+     * <dt>{@link Boolean}</dt><dd>for a boolean field</dd>

+     * <dt>{@link Character}</dt><dd>for a char field</dd>

+     * <dt>{@link Float}</dt><dd>for a float field</dd>

+     * <dt>{@link Double}</dt><dd>for a double field</dd>

+     * </dl>

      */

     public Object getValue()

     {

@@ -70,15 +74,18 @@
      * Currently this is used after deserializing static fields

      * to change the object reference to one having a link to the current snapshot.

      * @param object

-     * ObjectReference - for an object field

-     * Byte - for a byte field

-     * Short - for a short field

-     * Integer - for an int field

-     * Long - for a long field

-     * Boolean - for a boolean field

-     * Char - for a char field

-     * Float - for a float field

-     * Double - for a double field

+     * object is one of

+     * <dl>

+     * <dt>{@link ObjectReference}</dt><dd>for an object field</dd>

+     * <dt>{@link Byte}</dt><dd>for a byte field</dd>

+     * <dt>{@link Short}</dt><dd>for a short field</dd>

+     * <dt>{@link Integer}</dt><dd>for an int field</dd>

+     * <dt>{@link Long}</dt><dd>for a long field</dd>

+     * <dt>{@link Boolean}</dt><dd>for a boolean field</dd>

+     * <dt>{@link Character}</dt><dd>for a char field</dd>

+     * <dt>{@link Float}</dt><dd>for a float field</dd>

+     * <dt>{@link Double}</dt><dd>for a double field</dd>

+     * </dl>

      */

     public void setValue(Object object)

     {

diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/ObjectReference.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/ObjectReference.java
index 762a9dc..bbb2578 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/ObjectReference.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/model/ObjectReference.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2010 SAP AG and others.

+ * Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

@@ -9,6 +9,7 @@
  *

  * Contributors:

  *    SAP AG - initial API and implementation

+ *    Andrew Johnson (IBM Corporation) - lookup by address

  *******************************************************************************/

 package org.eclipse.mat.snapshot.model;

 

@@ -65,7 +66,25 @@
      */

     public IObject getObject() throws SnapshotException

     {

-        return snapshot.getObject(getObjectId());

+        int objectId;

+        try

+        {

+            objectId = getObjectId();

+        }

+        catch (SnapshotException e)

+        {

+            ObjectReference proxy = snapshot.getSnapshotAddons(ObjectReference.class);

+            if (proxy != null)

+            {

+                proxy.address = getObjectAddress();

+                return proxy.getObject();

+            }

+            else

+            {

+                throw e;

+            }

+        }

+        return snapshot.getObject(objectId);

     }

 

     /**

diff --git a/plugins/org.eclipse.mat.dtfj/src/org/eclipse/mat/dtfj/DTFJHeapObjectReader.java b/plugins/org.eclipse.mat.dtfj/src/org/eclipse/mat/dtfj/DTFJHeapObjectReader.java
index 1023c72..e8b97e0 100644
--- a/plugins/org.eclipse.mat.dtfj/src/org/eclipse/mat/dtfj/DTFJHeapObjectReader.java
+++ b/plugins/org.eclipse.mat.dtfj/src/org/eclipse/mat/dtfj/DTFJHeapObjectReader.java
@@ -1,10 +1,10 @@
 /*******************************************************************************

- * Copyright (c) 2009,2018 IBM Corporation.

+ * Copyright (c) 2009,2021 IBM Corporation.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

- * https://www.eclipse.org/legal/epl-2.0/
- *
+ * https://www.eclipse.org/legal/epl-2.0/

+ *

  * SPDX-License-Identifier: EPL-2.0

  *

  * Contributors:

@@ -24,6 +24,7 @@
 import org.eclipse.mat.SnapshotException;

 import org.eclipse.mat.dtfj.DTFJIndexBuilder.RuntimeInfo;

 import org.eclipse.mat.parser.IObjectReader;

+import org.eclipse.mat.parser.model.AbstractObjectImpl;

 import org.eclipse.mat.parser.model.ClassImpl;

 import org.eclipse.mat.parser.model.ClassLoaderImpl;

 import org.eclipse.mat.parser.model.InstanceImpl;

@@ -74,6 +75,8 @@
     private File file;

     /** All the key DTFJ data */

     private RuntimeInfo dtfjInfo;

+    /** the snapshot */

+    private ISnapshot snapshot;

     /**

      * whether to give up and throw an exception if reading object data fails,

      * or whether to carry on getting more data

@@ -88,6 +91,42 @@
     {

         // Close the dump

         DTFJIndexBuilder.DumpCache.releaseDump(file, dtfjInfo, true);

+        snapshot = null;

+    }

+

+    /**

+     * Used as a proxy for org.eclipse.mat.parser to read objects

+     * via address.

+     */

+    class ObjectAddressReference extends ObjectReference

+    {

+        private static final long serialVersionUID = 1L;

+

+        /**

+         * Constructor.

+         * The address will be filled in by {@link ObjectReference}.

+         */

+        public ObjectAddressReference()

+        {

+            super(snapshot, 0);

+        }

+

+        /**

+         * Create an IObject from the address.

+         */

+        public IObject getObject() throws SnapshotException

+        {

+            try

+            {

+                IObject o = DTFJHeapObjectReader.this.read(getObjectAddress(), -1, snapshot);

+                ((AbstractObjectImpl)o).setSnapshot(snapshot);

+                return o;

+            }

+            catch (IOException e)

+            {

+                throw new SnapshotException(e);

+            }

+        }

     }

 

     /**

@@ -132,6 +171,10 @@
         {

             return addon.cast(factory);

         }

+        else if (addon.isAssignableFrom(ObjectAddressReference.class))

+        {

+            return addon.cast(new ObjectAddressReference());

+        }

         else

         {

             return null;

@@ -147,6 +190,7 @@
     public void open(ISnapshot snapshot) throws IOException, SnapshotException

     {

         file = new File(snapshot.getSnapshotInfo().getPath());

+        this.snapshot = snapshot;

         SnapshotInfo snapinfo = snapshot.getSnapshotInfo();

         RuntimeInfo info = DTFJIndexBuilder.DumpCache.getDump(file, snapinfo.getProperty("$heapFormat")); //$NON-NLS-1$

         Serializable runtimeId = snapinfo.getProperty(DTFJIndexBuilder.RUNTIME_ID_KEY);

@@ -162,12 +206,17 @@
     public IObject read(int objectId, ISnapshot snapshot) throws SnapshotException, IOException

     {

         long addr = snapshot.mapIdToAddress(objectId);

+        return read(addr, objectId, snapshot);

+    }

+

+    IObject read(long addr, int objectId, ISnapshot snapshot) throws SnapshotException, IOException

+    {

         try

         {

             // DTFJ is not thread safe, but MAT is multi-threaded

             synchronized (dtfjInfo.getImage())

             {

-                if (getExtraInfo)

+                if (getExtraInfo && objectId != -1)

                 {

                     // See if the class looks like a method

                     ClassImpl cls = (ClassImpl) snapshot.getClassOf(objectId);

@@ -181,7 +230,7 @@
                 }

                 JavaObject jo = getJavaObjectByAddress0(addr);

                 IObject inst;

-                if (snapshot.isArray(objectId))

+                if (objectId == -1 ? jo.isArray() : snapshot.isArray(objectId))

                 {

                     inst = createArray(snapshot, objectId, addr, jo);

                 }

@@ -469,7 +518,7 @@
     private InstanceImpl createObject(ISnapshot snapshot, int objectId, long addr, JavaObject jo)

                     throws CorruptDataException, MemoryAccessException, SnapshotException

     {

-        ClassImpl cls = (ClassImpl) snapshot.getClassOf(objectId);

+        ClassImpl cls = findClass(snapshot, objectId, jo, addr);

         /*

          * Optimization - don't bother going to dump if there are no fields to

          * be found, and find the number of fields in advance if we do.

@@ -543,6 +592,52 @@
     }

 

     /**

+     * Find the {@link ClassImpl} which is the type of the object.

+     * @param snapshot the snapshot

+     * @param objectId for normal reads this is the object ID, -1 if unknown

+     * @param jo The DTFJ {@link JavaObject}

+     * @param addr the address of the object, for error messages

+     * @return the ClassImpl for the object, to allow the IOject to be constructed.

+     * @throws CorruptDataException

+     * @throws SnapshotException

+     */

+    private ClassImpl findClass(ISnapshot snapshot, int objectId, JavaObject jo, long addr)

+                    throws CorruptDataException, SnapshotException

+    {

+        ClassImpl cls;

+        if (objectId == -1)

+        {

+            JavaClass jc = jo.getJavaClass();

+            if (jc != null)

+            {

+                JavaObject jco = jc.getObject();

+                long caddr;

+                if (jco != null)

+                    caddr = jco.getID().getAddress();

+                else

+                    caddr = jc.getID().getAddress();

+                int clsId = snapshot.mapAddressToId(caddr);

+                IObject cls1 = snapshot.getObject(clsId);

+                if (cls1 instanceof ClassImpl)

+                    cls = (ClassImpl) cls1;

+                else

+                    throw new SnapshotException(MessageFormat.format(

+                                    Messages.DTFJHeapObjectReader_ErrorReadingObjectAtIndex, objectId, format(addr)));

+            }

+            else

+            {

+                throw new SnapshotException(MessageFormat.format(

+                                Messages.DTFJHeapObjectReader_ErrorReadingObjectAtIndex, objectId, format(addr)));

+            }

+        }

+        else

+        {

+            cls = (ClassImpl) snapshot.getClassOf(objectId);

+        }

+        return cls;

+    }

+

+    /**

      * Avoid problems with corrupt dumps

      * @param jc

      * @return

@@ -622,7 +717,7 @@
     private IObject createArray(ISnapshot snapshot, int objectId, long addr, JavaObject jo)

                     throws CorruptDataException, MemoryAccessException, SnapshotException

     {

-        ClassImpl cls = (ClassImpl) snapshot.getClassOf(objectId);

+        ClassImpl cls = findClass(snapshot, objectId, jo, addr);

         int offset = 0;

         int length = jo.getArraySize();

         // System.out.println("Array length "+length);

diff --git a/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofHeapObjectReader.java b/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofHeapObjectReader.java
index 8e67c90..a754b74 100644
--- a/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofHeapObjectReader.java
+++ b/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofHeapObjectReader.java
@@ -1,296 +1,301 @@
-/*******************************************************************************
- * Copyright (c) 2008, 2020 SAP AG and IBM Corporation.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- *    SAP AG - initial API and implementation
- *    Andrew Johnson (IBM Corporation) - additional properties
- *******************************************************************************/
-package org.eclipse.mat.hprof;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.mat.SnapshotException;
-import org.eclipse.mat.hprof.describer.Version;
-import org.eclipse.mat.hprof.extension.IRuntimeEnhancer;
-import org.eclipse.mat.hprof.ui.HprofPreferences;
-import org.eclipse.mat.parser.IObjectReader;
-import org.eclipse.mat.parser.index.IIndexReader;
-import org.eclipse.mat.parser.index.IndexReader;
-import org.eclipse.mat.parser.model.AbstractArrayImpl;
-import org.eclipse.mat.parser.model.ObjectArrayImpl;
-import org.eclipse.mat.parser.model.PrimitiveArrayImpl;
-import org.eclipse.mat.snapshot.ISnapshot;
-import org.eclipse.mat.snapshot.model.IObject;
-import org.eclipse.mat.snapshot.model.IPrimitiveArray;
-
-public class HprofHeapObjectReader implements IObjectReader
-{
-    public static final String VERSION_PROPERTY = "hprof.version"; //$NON-NLS-1$
-    public static final String HPROF_LENGTH_PROPERTY = "hprof.length"; //$NON-NLS-1$
-
-    private ISnapshot snapshot;
-    private HprofRandomAccessParser hprofDump;
-    private IIndexReader.IOne2LongIndex o2hprof;
-    private List<IRuntimeEnhancer> enhancers;
-
-    public void open(ISnapshot snapshot) throws IOException
-    {
-        this.snapshot = snapshot;
-
-        Version version = Version.valueOf((String) snapshot.getSnapshotInfo()
-                        .getProperty(VERSION_PROPERTY));
-
-        HprofPreferences.HprofStrictness strictnessPreference = HprofPreferences.getCurrentStrictness();
-        Long olen = (Long)snapshot.getSnapshotInfo().getProperty(HPROF_LENGTH_PROPERTY);
-        long len = (olen != null) ? olen : -1;
-
-        this.hprofDump = new HprofRandomAccessParser(new File(snapshot.getSnapshotInfo().getPath()), //
-                        version, //
-                        snapshot.getSnapshotInfo().getIdentifierSize(), len, strictnessPreference);
-        this.o2hprof = new IndexReader.LongIndexReader(new File(snapshot.getSnapshotInfo().getPrefix()
-                        + "o2hprof.index")); //$NON-NLS-1$
-
-        this.enhancers = new ArrayList<IRuntimeEnhancer>();
-        for (EnhancerRegistry.Enhancer enhancer : EnhancerRegistry.instance().delegates())
-        {
-            IRuntimeEnhancer runtime = enhancer.runtime();
-            if (runtime != null)
-                this.enhancers.add(runtime);
-        }
-    }
-
-    public long[] readObjectArrayContent(ObjectArrayImpl array, int offset, int length) throws IOException,
-                    SnapshotException
-    {
-        Object info = array.getInfo();
-
-        if (info instanceof ArrayDescription.Offline)
-        {
-            ArrayDescription.Offline description = (ArrayDescription.Offline) info;
-
-            long[] answer = (long[]) description.getLazyReadContent();
-            if (answer == null)
-            {
-                answer = hprofDump.readObjectArray(description, offset, length);
-
-                // save content if fully read...
-                if (offset == 0 && length == array.getLength())
-                    description.setLazyReadContent(answer);
-
-                return answer;
-            }
-            else
-            {
-                return (long[]) fragment(array, answer, offset, length);
-            }
-        }
-        else if (info instanceof long[])
-        {
-            return (long[]) fragment(array, info, offset, length);
-        }
-        else
-        {
-            throw new IllegalArgumentException();
-        }
-    }
-
-    public Object readPrimitiveArrayContent(PrimitiveArrayImpl array, int offset, int length) throws IOException,
-                    SnapshotException
-    {
-        Object info = array.getInfo();
-
-        if (info instanceof ArrayDescription.Offline)
-        {
-            ArrayDescription.Offline description = (ArrayDescription.Offline) info;
-
-            Object content = description.getLazyReadContent();
-            if (content == null)
-            {
-                content = convert(array, hprofDump.readPrimitiveArray(description, offset, length));
-
-                // save content if fully read...
-                if (offset == 0 && length == array.getLength())
-                    description.setLazyReadContent(content);
-
-                return content;
-            }
-            else
-            {
-                return fragment(array, content, offset, length);
-            }
-        }
-        else if (info instanceof ArrayDescription.Raw)
-        {
-            ArrayDescription.Raw description = (ArrayDescription.Raw) info;
-            Object content = convert(array, description.getContent());
-            array.setInfo(content);
-
-            return fragment(array, content, offset, length);
-        }
-        else
-        {
-            return fragment(array, info, offset, length);
-        }
-    }
-
-    private Object convert(PrimitiveArrayImpl array, byte[] content)
-    {
-        int type = array.getType();
-        if (type == IObject.Type.BYTE)
-            return content;
-
-        int elementSize = IPrimitiveArray.ELEMENT_SIZE[type];
-        int length = content.length / elementSize;
-
-        Object answer = Array.newInstance(IPrimitiveArray.COMPONENT_TYPE[type], length);
-
-        int index = 0;
-        for (int ii = 0; ii < content.length; ii += elementSize)
-        {
-            switch (type)
-            {
-                case IObject.Type.BOOLEAN:
-                    Array.set(answer, index, content[ii] != 0);
-                    break;
-                case IObject.Type.CHAR:
-                    Array.set(answer, index, readChar(content, ii));
-                    break;
-                case IObject.Type.FLOAT:
-                    Array.set(answer, index, readFloat(content, ii));
-                    break;
-                case IObject.Type.DOUBLE:
-                    Array.set(answer, index, readDouble(content, ii));
-                    break;
-                case IObject.Type.SHORT:
-                    Array.set(answer, index, readShort(content, ii));
-                    break;
-                case IObject.Type.INT:
-                    Array.set(answer, index, readInt(content, ii));
-                    break;
-                case IObject.Type.LONG:
-                    Array.set(answer, index, readLong(content, ii));
-                    break;
-            }
-
-            index++;
-        }
-
-        return answer;
-    }
-
-    private Object fragment(AbstractArrayImpl array, Object content, int offset, int length)
-    {
-        if (offset == 0 && length == array.getLength())
-            return content;
-
-        Object answer = Array.newInstance(content.getClass().getComponentType(), length);
-        System.arraycopy(content, offset, answer, 0, length);
-        return answer;
-    }
-
-    public IObject read(int objectId, ISnapshot snapshot) throws SnapshotException, IOException
-    {
-        long filePosition = o2hprof.get(objectId);
-        return hprofDump.read(objectId, filePosition, snapshot);
-    }
-
-    /**
-     * Returns extra data to be provided by
-     * {@link ISnapshot#getSnapshotAddons(Class addon)}. Also can be returned
-     * via {@link org.eclipse.mat.query.annotations.Argument}.
-     * 
-     * @see org.eclipse.mat.parser.IObjectReader#getAddon(Class)
-     * @param addon
-     *            the type of the extra data required from the dump.
-     *            HprofHeapObjectReader can be extended using an
-     *            {@link IRuntimeEnhancer} extension to return extra data.
-     * @return the extra data
-     */
-    public <A> A getAddon(Class<A> addon) throws SnapshotException
-    {
-        for (IRuntimeEnhancer enhancer : enhancers)
-        {
-            A answer = enhancer.getAddon(snapshot, addon);
-            if (answer != null)
-                return answer;
-        }
-        return null;
-    }
-
-    public void close() throws IOException
-    {
-        try
-        {
-            hprofDump.close();
-        }
-        catch (IOException ignore)
-        {}
-
-        try
-        {
-            o2hprof.close();
-        }
-        catch (IOException ignore)
-        {}
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // conversion routines
-    // //////////////////////////////////////////////////////////////
-
-    private short readShort(byte[] data, int offset)
-    {
-        int b1 = (data[offset] & 0xff);
-        int b2 = (data[offset + 1] & 0xff);
-        return (short) ((b1 << 8) + b2);
-    }
-
-    private char readChar(byte[] data, int offset)
-    {
-        int b1 = (data[offset] & 0xff);
-        int b2 = (data[offset + 1] & 0xff);
-        return (char) ((b1 << 8) + b2);
-    }
-
-    private int readInt(byte[] data, int offset)
-    {
-        int ch1 = data[offset] & 0xff;
-        int ch2 = data[offset + 1] & 0xff;
-        int ch3 = data[offset + 2] & 0xff;
-        int ch4 = data[offset + 3] & 0xff;
-        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
-    }
-
-    private float readFloat(byte[] data, int offset)
-    {
-        return Float.intBitsToFloat(readInt(data, offset));
-    }
-
-    private long readLong(byte[] data, int offset)
-    {
-        return ((((long) data[offset] & 0xff) << 56) + //
-                        ((long) (data[offset + 1] & 0xff) << 48) + //
-                        ((long) (data[offset + 2] & 0xff) << 40) + //
-                        ((long) (data[offset + 3] & 0xff) << 32) + //
-                        ((long) (data[offset + 4] & 0xff) << 24) + //
-                        ((data[offset + 5] & 0xff) << 16) + //
-                        ((data[offset + 6] & 0xff) << 8) + //
-        ((data[offset + 7] & 0xff) << 0));
-    }
-
-    private double readDouble(byte[] data, int offset)
-    {
-        return Double.longBitsToDouble(readLong(data, offset));
-    }
-
-}
+/*******************************************************************************

+ * Copyright (c) 2008, 2021 SAP AG and IBM Corporation.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License 2.0

+ * which accompanies this distribution, and is available at

+ * https://www.eclipse.org/legal/epl-2.0/

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ * Contributors:

+ *    SAP AG - initial API and implementation

+ *    Andrew Johnson (IBM Corporation) - additional properties

+ *******************************************************************************/

+package org.eclipse.mat.hprof;

+

+import java.io.File;

+import java.io.IOException;

+import java.lang.reflect.Array;

+import java.util.ArrayList;

+import java.util.List;

+

+import org.eclipse.mat.SnapshotException;

+import org.eclipse.mat.hprof.describer.Version;

+import org.eclipse.mat.hprof.extension.IRuntimeEnhancer;

+import org.eclipse.mat.hprof.ui.HprofPreferences;

+import org.eclipse.mat.parser.IObjectReader;

+import org.eclipse.mat.parser.index.IIndexReader;

+import org.eclipse.mat.parser.index.IndexReader;

+import org.eclipse.mat.parser.model.AbstractArrayImpl;

+import org.eclipse.mat.parser.model.ObjectArrayImpl;

+import org.eclipse.mat.parser.model.PrimitiveArrayImpl;

+import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.model.IObject;

+import org.eclipse.mat.snapshot.model.IPrimitiveArray;

+

+public class HprofHeapObjectReader implements IObjectReader

+{

+    public static final String VERSION_PROPERTY = "hprof.version"; //$NON-NLS-1$

+    public static final String HPROF_LENGTH_PROPERTY = "hprof.length"; //$NON-NLS-1$

+    public static final String HPROF_HEAP_START = "hprof.heap.start"; //$NON-NLS-1$

+

+    private ISnapshot snapshot;

+    private HprofRandomAccessParser hprofDump;

+    private IIndexReader.IOne2LongIndex o2hprof;

+    private List<IRuntimeEnhancer> enhancers;

+

+    public void open(ISnapshot snapshot) throws IOException

+    {

+        this.snapshot = snapshot;

+

+        Version version = Version.valueOf((String) snapshot.getSnapshotInfo()

+                        .getProperty(VERSION_PROPERTY));

+

+        HprofPreferences.HprofStrictness strictnessPreference = HprofPreferences.getCurrentStrictness();

+        Long olen = (Long)snapshot.getSnapshotInfo().getProperty(HPROF_LENGTH_PROPERTY);

+        long len = (olen != null) ? olen : -1;

+

+        this.hprofDump = new HprofRandomAccessParser(new File(snapshot.getSnapshotInfo().getPath()), //

+                        version, //

+                        snapshot.getSnapshotInfo().getIdentifierSize(), len, strictnessPreference);

+        this.o2hprof = new IndexReader.LongIndexReader(new File(snapshot.getSnapshotInfo().getPrefix()

+                        + "o2hprof.index")); //$NON-NLS-1$

+

+        this.enhancers = new ArrayList<IRuntimeEnhancer>();

+        for (EnhancerRegistry.Enhancer enhancer : EnhancerRegistry.instance().delegates())

+        {

+            IRuntimeEnhancer runtime = enhancer.runtime();

+            if (runtime != null)

+                this.enhancers.add(runtime);

+        }

+    }

+

+    public long[] readObjectArrayContent(ObjectArrayImpl array, int offset, int length) throws IOException,

+                    SnapshotException

+    {

+        Object info = array.getInfo();

+

+        if (info instanceof ArrayDescription.Offline)

+        {

+            ArrayDescription.Offline description = (ArrayDescription.Offline) info;

+

+            long[] answer = (long[]) description.getLazyReadContent();

+            if (answer == null)

+            {

+                answer = hprofDump.readObjectArray(description, offset, length);

+

+                // save content if fully read...

+                if (offset == 0 && length == array.getLength())

+                    description.setLazyReadContent(answer);

+

+                return answer;

+            }

+            else

+            {

+                return (long[]) fragment(array, answer, offset, length);

+            }

+        }

+        else if (info instanceof long[])

+        {

+            return (long[]) fragment(array, info, offset, length);

+        }

+        else

+        {

+            throw new IllegalArgumentException();

+        }

+    }

+

+    public Object readPrimitiveArrayContent(PrimitiveArrayImpl array, int offset, int length) throws IOException,

+                    SnapshotException

+    {

+        Object info = array.getInfo();

+

+        if (info instanceof ArrayDescription.Offline)

+        {

+            ArrayDescription.Offline description = (ArrayDescription.Offline) info;

+

+            Object content = description.getLazyReadContent();

+            if (content == null)

+            {

+                content = convert(array, hprofDump.readPrimitiveArray(description, offset, length));

+

+                // save content if fully read...

+                if (offset == 0 && length == array.getLength())

+                    description.setLazyReadContent(content);

+

+                return content;

+            }

+            else

+            {

+                return fragment(array, content, offset, length);

+            }

+        }

+        else if (info instanceof ArrayDescription.Raw)

+        {

+            ArrayDescription.Raw description = (ArrayDescription.Raw) info;

+            Object content = convert(array, description.getContent());

+            array.setInfo(content);

+

+            return fragment(array, content, offset, length);

+        }

+        else

+        {

+            return fragment(array, info, offset, length);

+        }

+    }

+

+    private Object convert(PrimitiveArrayImpl array, byte[] content)

+    {

+        int type = array.getType();

+        if (type == IObject.Type.BYTE)

+            return content;

+

+        int elementSize = IPrimitiveArray.ELEMENT_SIZE[type];

+        int length = content.length / elementSize;

+

+        Object answer = Array.newInstance(IPrimitiveArray.COMPONENT_TYPE[type], length);

+

+        int index = 0;

+        for (int ii = 0; ii < content.length; ii += elementSize)

+        {

+            switch (type)

+            {

+                case IObject.Type.BOOLEAN:

+                    Array.set(answer, index, content[ii] != 0);

+                    break;

+                case IObject.Type.CHAR:

+                    Array.set(answer, index, readChar(content, ii));

+                    break;

+                case IObject.Type.FLOAT:

+                    Array.set(answer, index, readFloat(content, ii));

+                    break;

+                case IObject.Type.DOUBLE:

+                    Array.set(answer, index, readDouble(content, ii));

+                    break;

+                case IObject.Type.SHORT:

+                    Array.set(answer, index, readShort(content, ii));

+                    break;

+                case IObject.Type.INT:

+                    Array.set(answer, index, readInt(content, ii));

+                    break;

+                case IObject.Type.LONG:

+                    Array.set(answer, index, readLong(content, ii));

+                    break;

+            }

+

+            index++;

+        }

+

+        return answer;

+    }

+

+    private Object fragment(AbstractArrayImpl array, Object content, int offset, int length)

+    {

+        if (offset == 0 && length == array.getLength())

+            return content;

+

+        Object answer = Array.newInstance(content.getClass().getComponentType(), length);

+        System.arraycopy(content, offset, answer, 0, length);

+        return answer;

+    }

+

+    public IObject read(int objectId, ISnapshot snapshot) throws SnapshotException, IOException

+    {

+        long filePosition = o2hprof.get(objectId);

+        return hprofDump.read(objectId, filePosition, snapshot, o2hprof);

+    }

+

+    /**

+     * Returns extra data to be provided by

+     * {@link ISnapshot#getSnapshotAddons(Class addon)}. Also can be returned

+     * via {@link org.eclipse.mat.query.annotations.Argument}.

+     * 

+     * @see org.eclipse.mat.parser.IObjectReader#getAddon(Class)

+     * @param addon

+     *            the type of the extra data required from the dump.

+     *            HprofHeapObjectReader can be extended using an

+     *            {@link IRuntimeEnhancer} extension to return extra data.

+     * @return the extra data

+     */

+    public <A> A getAddon(Class<A> addon) throws SnapshotException

+    {

+        for (IRuntimeEnhancer enhancer : enhancers)

+        {

+            A answer = enhancer.getAddon(snapshot, addon);

+            if (answer != null)

+                return answer;

+        }

+        if (addon.isAssignableFrom(HprofRandomAccessParser.ObjectAddressReference.class))

+        {

+            return addon.cast(new HprofRandomAccessParser.ObjectAddressReference(snapshot, hprofDump, o2hprof, Long.MIN_VALUE));

+        }

+        return null;

+    }

+

+    public void close() throws IOException

+    {

+        try

+        {

+            hprofDump.close();

+        }

+        catch (IOException ignore)

+        {}

+

+        try

+        {

+            o2hprof.close();

+        }

+        catch (IOException ignore)

+        {}

+    }

+

+    // //////////////////////////////////////////////////////////////

+    // conversion routines

+    // //////////////////////////////////////////////////////////////

+

+    private short readShort(byte[] data, int offset)

+    {

+        int b1 = (data[offset] & 0xff);

+        int b2 = (data[offset + 1] & 0xff);

+        return (short) ((b1 << 8) + b2);

+    }

+

+    private char readChar(byte[] data, int offset)

+    {

+        int b1 = (data[offset] & 0xff);

+        int b2 = (data[offset + 1] & 0xff);

+        return (char) ((b1 << 8) + b2);

+    }

+

+    private int readInt(byte[] data, int offset)

+    {

+        int ch1 = data[offset] & 0xff;

+        int ch2 = data[offset + 1] & 0xff;

+        int ch3 = data[offset + 2] & 0xff;

+        int ch4 = data[offset + 3] & 0xff;

+        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));

+    }

+

+    private float readFloat(byte[] data, int offset)

+    {

+        return Float.intBitsToFloat(readInt(data, offset));

+    }

+

+    private long readLong(byte[] data, int offset)

+    {

+        return ((((long) data[offset] & 0xff) << 56) + //

+                        ((long) (data[offset + 1] & 0xff) << 48) + //

+                        ((long) (data[offset + 2] & 0xff) << 40) + //

+                        ((long) (data[offset + 3] & 0xff) << 32) + //

+                        ((long) (data[offset + 4] & 0xff) << 24) + //

+                        ((data[offset + 5] & 0xff) << 16) + //

+                        ((data[offset + 6] & 0xff) << 8) + //

+        ((data[offset + 7] & 0xff) << 0));

+    }

+

+    private double readDouble(byte[] data, int offset)

+    {

+        return Double.longBitsToDouble(readLong(data, offset));

+    }

+

+}

diff --git a/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofParserHandlerImpl.java b/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofParserHandlerImpl.java
index 814381d..782001a 100644
--- a/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofParserHandlerImpl.java
+++ b/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofParserHandlerImpl.java
@@ -1,1168 +1,1176 @@
-/*******************************************************************************
- * Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- *    SAP AG - initial API and implementation
- *    Andrew Johnson - bug fix for missing classes
- *    Netflix (Jason Koch) - refactors for increased performance and concurrency
- *******************************************************************************/
-package org.eclipse.mat.hprof;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.regex.Pattern;
-
-import org.eclipse.mat.SnapshotException;
-import org.eclipse.mat.collect.HashMapIntObject;
-import org.eclipse.mat.collect.HashMapLongObject;
-import org.eclipse.mat.collect.HashMapLongObject.Entry;
-import org.eclipse.mat.collect.IteratorLong;
-import org.eclipse.mat.hprof.describer.Version;
-import org.eclipse.mat.hprof.ui.HprofPreferences;
-import org.eclipse.mat.parser.IPreliminaryIndex;
-import org.eclipse.mat.parser.index.IIndexReader.IOne2LongIndex;
-import org.eclipse.mat.parser.index.IndexManager.Index;
-import org.eclipse.mat.parser.index.IndexWriter;
-import org.eclipse.mat.parser.index.IndexWriter.IntArray1NWriter;
-import org.eclipse.mat.parser.index.IndexWriter.IntIndexCollector;
-import org.eclipse.mat.parser.index.IndexWriter.LongIndexCollector;
-import org.eclipse.mat.parser.index.IndexWriter.LongIndexStreamer;
-import org.eclipse.mat.parser.model.ClassImpl;
-import org.eclipse.mat.parser.model.PrimitiveArrayImpl;
-import org.eclipse.mat.parser.model.XGCRootInfo;
-import org.eclipse.mat.parser.model.XSnapshotInfo;
-import org.eclipse.mat.snapshot.UnreachableObjectsHistogram;
-import org.eclipse.mat.snapshot.model.Field;
-import org.eclipse.mat.snapshot.model.FieldDescriptor;
-import org.eclipse.mat.snapshot.model.GCRootInfo;
-import org.eclipse.mat.snapshot.model.IClass;
-import org.eclipse.mat.snapshot.model.IObject;
-import org.eclipse.mat.snapshot.model.IPrimitiveArray;
-import org.eclipse.mat.snapshot.model.ObjectReference;
-import org.eclipse.mat.util.IProgressListener;
-import org.eclipse.mat.util.MessageUtil;
-
-public class HprofParserHandlerImpl implements IHprofParserHandler
-{
-    // private String prefix;
-    private Version version;
-
-    private XSnapshotInfo info = new XSnapshotInfo();
-
-    private Map<String, List<ClassImpl>> classesByName = new HashMap<String, List<ClassImpl>>();
-    private HashMapLongObject<ClassImpl> classesByAddress = new HashMapLongObject<ClassImpl>();
-
-    private HashMapLongObject<List<XGCRootInfo>> gcRoots = new HashMapLongObject<List<XGCRootInfo>>(200);
-
-    private IndexWriter.Identifier identifiers = null;
-    private IntArray1NWriter outbound = null;
-    private IntIndexCollector object2classId = null;
-    private LongIndexCollector object2position = null;
-    private IndexWriter.SizeIndexCollectorUncompressed array2size = null;
-
-    private HashMap<Long, Boolean> requiredArrayClassIDs = new HashMap<Long, Boolean>();
-    private HashMap<Long, Integer> requiredClassIDs = new HashMap<Long, Integer>();
-    private IClass[] primitiveArrays = new IClass[IPrimitiveArray.TYPE.length];
-    private boolean[] requiredPrimitiveArrays = new boolean[IPrimitiveArray.COMPONENT_TYPE.length];
-
-    private HashMapLongObject<HashMapLongObject<List<XGCRootInfo>>> threadAddressToLocals = new HashMapLongObject<HashMapLongObject<List<XGCRootInfo>>>();
-
-    /** Keep track of numbers and size of discarded objects */
-    private ConcurrentHashMap<Integer, ClassImpl> discardedObjectsByClass = new ConcurrentHashMap<Integer, ClassImpl>();
-
-    // The size of (possibly compressed) references in the heap
-    private int refSize;
-    // The size of uncompressed pointers in the object headers in the heap
-    private int pointerSize;
-    // The alignment between successive objects
-    private int objectAlign;
-    // New size of classes including per-instance fields
-    private final boolean NEWCLASSSIZE = HprofPreferences.useAdditionalClassReferences();
-    // Largest offset into HPROF file
-    private long maxFilePosition = 0;
-
-    /** Which class instances to possibly discard */ 
-    private Pattern discardPattern = Pattern.compile("char\\[\\]|java\\.lang\\.String"); //$NON-NLS-1$
-    /** How often to discard */
-    private double discardRatio = 0.0;
-    /** Select which group of objects are discarded */
-    private double discardOffset = 0.0;
-    /** Random number seed for choosing discards */
-    private long discardSeed = 1;
-    /** Random number generator to choose what to discard */
-    private Random rand = new Random(discardSeed);
-
-    // //////////////////////////////////////////////////////////////
-    // lifecycle
-    // //////////////////////////////////////////////////////////////
-
-    public void beforePass1(XSnapshotInfo snapshotInfo) throws IOException
-    {
-        this.info = snapshotInfo;
-        this.identifiers = new IndexWriter.Identifier();
-        if (info.getProperty("discard_ratio") instanceof Integer) //$NON-NLS-1$
-        {
-            discardRatio = (Integer)info.getProperty("discard_ratio") / 100.0; //$NON-NLS-1$
-            if (info.getProperty("discard_offset") instanceof Integer) //$NON-NLS-1$
-            {
-                discardOffset = (Integer)info.getProperty("discard_offset") / 100.0; //$NON-NLS-1$
-            }
-            else
-            {
-                info.setProperty("discard_offset", (int)Math.round(discardOffset * 100)); //$NON-NLS-1$
-            }
-            if (info.getProperty("discard_seed") instanceof Integer) //$NON-NLS-1$
-            {
-                discardSeed = (Integer)info.getProperty("discard_seed"); //$NON-NLS-1$
-            }
-            else
-            {
-                info.setProperty("discard_seed", discardSeed); //$NON-NLS-1$
-            }
-            rand = new Random(discardSeed);
-            if (info.getProperty("discard_pattern") instanceof String) //$NON-NLS-1$
-            {
-                discardPattern = Pattern.compile((String)info.getProperty("discard_pattern")); //$NON-NLS-1$
-            }
-            else
-            {
-                info.setProperty("discard_pattern", discardPattern.toString()); //$NON-NLS-1$
-            }
-        }
-    }
-
-    public void beforePass2(IProgressListener monitor) throws IOException, SnapshotException
-    {
-        // add dummy address for system class loader object
-        identifiers.add(0);
-
-        // sort and assign preliminary object ids
-        identifiers.sort();
-
-        // See what the actual object alignment is
-        calculateAlignment();
-
-        // Set property to show if compressed oops are used on x64 bit dumps
-        if (pointerSize == 8) // if x64 bit dump
-        {
-            info.setProperty("$useCompressedOops", refSize == 4); //$NON-NLS-1$
-        }
-
-        // if necessary, create required classes not contained in the heap
-        createRequiredFakeClasses();
-
-        // informational messages to the user
-        monitor.sendUserMessage(IProgressListener.Severity.INFO, MessageUtil.format(
-                        Messages.HprofParserHandlerImpl_HeapContainsObjects, info.getPath(), identifiers.size()), null);
-
-        // if instance dumps for classes are present, then fix up the classes
-        addTypesAndDummyStatics();
-
-        int maxClassId = 0;
-
-        // calculate instance size for all classes
-        for (Iterator<?> e = classesByAddress.values(); e.hasNext();)
-        {
-            ClassImpl clazz = (ClassImpl) e.next();
-            int index = identifiers.reverse(clazz.getObjectAddress());
-            clazz.setObjectId(index);
-
-            maxClassId = Math.max(maxClassId, index);
-
-            clazz.setHeapSizePerInstance(calculateInstanceSize(clazz));
-            clazz.setUsedHeapSize(calculateClassSize(clazz));
-        }
-
-        // create index writers
-        outbound = new IntArray1NWriter(this.identifiers.size(), Index.OUTBOUND.getFile(info.getPrefix()
-                        + "temp."));//$NON-NLS-1$
-        object2classId = new IntIndexCollector(this.identifiers.size(), IndexWriter
-                        .mostSignificantBit(maxClassId));
-        object2position = new LongIndexCollector(this.identifiers.size(), IndexWriter
-                        .mostSignificantBit(maxFilePosition));
-        array2size = new IndexWriter.SizeIndexCollectorUncompressed(this.identifiers.size());
-
-        // java.lang.Class needs some special treatment so that object2classId
-        // is written correctly
-        List<ClassImpl> javaLangClasses = classesByName.get(ClassImpl.JAVA_LANG_CLASS);
-        ClassImpl javaLangClass = javaLangClasses.get(0);
-        javaLangClass.setObjectId(identifiers.reverse(javaLangClass.getObjectAddress()));
-
-        // log references for classes
-        for (Iterator<?> e = classesByAddress.values(); e.hasNext();)
-        {
-            ClassImpl clazz = (ClassImpl) e.next();
-            clazz.setSuperClassIndex(identifiers.reverse(clazz.getSuperClassAddress()));
-            clazz.setClassLoaderIndex(identifiers.reverse(clazz.getClassLoaderAddress()));
-
-            // [INFO] in newer jdk hprof files, the boot class loader
-            // has an address other than 0. The class loader instances
-            // is still not contained in the hprof file
-            if (clazz.getClassLoaderId() < 0)
-            {
-                clazz.setClassLoaderAddress(0);
-                clazz.setClassLoaderIndex(identifiers.reverse(0));
-            }
-
-            boolean skipLogRefs = false;
-            // add class instance - if not set by pass1 from an instance_dump for the class
-            if (clazz.getClazz() == null)
-            {
-                clazz.setClassInstance(javaLangClass);
-                if (NEWCLASSSIZE)
-                {
-                    // Recalculate the clazz heap size based on also java.lang.Class fields
-                    // No need to do this for classes of other types as
-                    // these have object instance records with fields converted to statics
-                    clazz.setUsedHeapSize(clazz.getUsedHeapSize() + clazz.getClazz().getHeapSizePerInstance());
-                }
-                clazz.getClazz().addInstance(clazz.getUsedHeapSize());
-            }
-            else
-            {
-                // References for classes with instance dump records will be generated in pass2
-                skipLogRefs = true;
-            }
-
-            // resolve super class
-            ClassImpl superclass = lookupClass(clazz.getSuperClassAddress());
-            if (superclass != null)
-                superclass.addSubClass(clazz);
-
-            object2classId.set(clazz.getObjectId(), clazz.getClazz().getObjectId());
-
-            if (!skipLogRefs)
-                outbound.log(identifiers, clazz.getObjectId(), clazz.getReferences());
-        }
-
-        // report dependencies for system class loader
-        // (if no classes use this class loader, cleanup garbage will remove it
-        // again)
-        ClassImpl classLoaderClass = this.classesByName.get(IClass.JAVA_LANG_CLASSLOADER).get(0);
-        HeapObject heapObject = new HeapObject(0, classLoaderClass, classLoaderClass
-                        .getHeapSizePerInstance());
-        heapObject.references.add(classLoaderClass.getObjectAddress());
-        this.addObject(heapObject, true);
-
-    }
-
-    /**
-     * Possible HPROF extension:
-     * classes also with instance dump records.
-     * The classes could be of a type other than java.lang.Class
-     * There could also be per-instance fields defined by their type.
-     * Those values can be made accessible to MAT by creating pseudo-static fields.
-     */
-    private void addTypesAndDummyStatics()
-    {
-        // Set type (and size?) for classes with object instances
-        // These will have had the type set by Pass1Parser
-        for (Iterator<Entry<ClassImpl>> it = classesByAddress.entries(); it.hasNext();)
-        {
-            Entry<ClassImpl> e = it.next();
-            ClassImpl cl = e.getValue();
-            ClassImpl type = cl.getClazz();
-            if (type != null)
-            {
-                List<FieldDescriptor> newStatics = new ArrayList<FieldDescriptor>(cl.getStaticFields());
-                List<IClass> icls = resolveClassHierarchy(type.getClassAddress());
-                for (IClass tcl : icls)
-                {
-                    for (FieldDescriptor fd : tcl.getFieldDescriptors())
-                    {
-                        // Create pseudo-static field
-                        Field st = new Field("<" + fd.getName() + ">", fd.getType(), null); //$NON-NLS-1$ //$NON-NLS-2$
-                        newStatics.add(st);
-                    }
-                }
-                if (newStatics.size() != cl.getStaticFields().size())
-                {
-                    ClassImpl newcl = new ClassImpl(cl.getObjectAddress(), cl.getName(), cl.getSuperClassAddress(),
-                                    cl.getClassLoaderAddress(), newStatics.toArray(new Field[newStatics.size()]),
-                                    cl.getFieldDescriptors().toArray(new FieldDescriptor[0]));
-                    newcl.setClassInstance(type);
-                    // Fix up the existing lookups
-                    classesByAddress.put(e.getKey(), newcl);
-                    List<ClassImpl>nms = classesByName.get(cl.getName());
-                    for (int i = 0; i < nms.size(); ++i)
-                    {
-                        if (nms.get(i) == cl)
-                            nms.set(i, newcl);
-                    }
-                }
-            }
-        }
-
-        // Set type to new type with the dummy static fields
-        for (Iterator<Entry<ClassImpl>> it = classesByAddress.entries(); it.hasNext();)
-        {
-            Entry<ClassImpl> e = it.next();
-            ClassImpl cl = e.getValue();
-            ClassImpl type = cl.getClazz();
-            if (type != null)
-            {
-                ClassImpl type2 = classesByAddress.get(type.getObjectAddress());
-                // Actual object test, not equality as we need to maintain linkage to the new class
-                if (type != type2)
-                {
-                    cl.setClassInstance(type2);
-                }
-            }
-        }
-    }
-
-    /**
-     * Calculate possible restrictions on object alignment by finding the GCD of differences
-     * between object addresses (ignoring address 0).
-     */
-    private void calculateAlignment()
-    {
-        // Minimum alignment of 8 bytes
-        final int minAlign = 8;
-        // Maximum alignment of 256 bytes
-        final int maxAlign = 256;
-        long prev = 0;
-        long align = 0;
-        for (IteratorLong it = identifiers.iterator(); it.hasNext(); )
-        {
-            long next = it.next();
-            if (next == 0)
-                continue;
-            long diff = next - prev;
-            prev = next;
-            if (next == diff)
-                continue;
-            if (align == 0)
-            {
-                align = diff;
-            }
-            else
-            {
-                long mx = Math.max(align, diff);
-                long mn = Math.min(align, diff);
-                long d = mx % mn;
-                while (d != 0) {
-                    mx = mn;
-                    mn = d;
-                    d = mx % mn;
-                }
-                align = mn;
-                // Minimum alignment
-                if (align <= minAlign)
-                    break;
-            }
-        }
-        // Sanitise the alignment
-        objectAlign = Math.max((int)Math.min(align, maxAlign), minAlign);
-    }
-
-    private void createRequiredFakeClasses() throws IOException, SnapshotException
-    {
-        // we know: system class loader has object address 0
-        long nextObjectAddress = 0;
-        // For generating the fake class names
-        int clsid = 0;
-        // java.lang.Object for the superclass
-        List<ClassImpl>jlos = classesByName.get("java.lang.Object"); //$NON-NLS-1$
-        long jlo = jlos == null || jlos.isEmpty() ? 0 : jlos.get(0).getObjectAddress();
-        // Fake java.lang.Class, java.lang.ClassLoader for later
-        String clss[] = {ClassImpl.JAVA_LANG_CLASS, ClassImpl.JAVA_LANG_CLASSLOADER};
-        for (String cls : clss)
-        {
-            List<ClassImpl>jlcs = classesByName.get(cls);
-            if (jlcs == null || jlcs.isEmpty())
-            {
-                while (identifiers.reverse(++nextObjectAddress) >= 0)
-                {}
-                IClass type = new ClassImpl(nextObjectAddress, cls, jlo, 0, new Field[0], new FieldDescriptor[0]);
-                addFakeClass((ClassImpl) type, -1);
-            }
-        }
-
-        // create required (fake) classes for arrays
-        if (!requiredArrayClassIDs.isEmpty())
-        {
-            for (long arrayClassID : requiredArrayClassIDs.keySet())
-            {
-                IClass arrayType = lookupClass(arrayClassID);
-                if (arrayType == null)
-                {
-                    int objectId = identifiers.reverse(arrayClassID);
-                    if (objectId >= 0)
-                    {
-                        String msg = MessageUtil.format(Messages.HprofParserHandlerImpl_Error_ExpectedClassSegment,
-                                        Long.toHexString(arrayClassID));
-                        throw new SnapshotException(msg);
-                    }
-
-                    arrayType = new ClassImpl(arrayClassID, "unknown-class-"+clsid+"[]", jlo, 0, new Field[0], //$NON-NLS-1$ //$NON-NLS-2$
-                                    new FieldDescriptor[0]);
-                    ++clsid;
-                    addFakeClass((ClassImpl) arrayType, -1);
-                }
-            }
-        }
-        requiredArrayClassIDs = null;
-
-        for(int arrayType = 0; arrayType < requiredPrimitiveArrays.length; arrayType++)
-        {
-            if (requiredPrimitiveArrays[arrayType])
-            {
-                String name = IPrimitiveArray.TYPE[arrayType];
-                IClass clazz = lookupClassByName(name, true);
-                if (clazz == null)
-                {
-                    while (identifiers.reverse(++nextObjectAddress) >= 0)
-                    {}
-
-                    clazz = new ClassImpl(nextObjectAddress, name, jlo, 0, new Field[0], new FieldDescriptor[0]);
-                    addFakeClass((ClassImpl) clazz, -1);
-                }
-                primitiveArrays[arrayType] = clazz;
-            }
-        }
-
-        // create required (fake) classes for objects
-        if (!requiredClassIDs.isEmpty())
-        {
-            for (Map.Entry<Long, Integer> e : requiredClassIDs.entrySet())
-            {
-                long classID = e.getKey();
-                IClass type = lookupClass(classID);
-                if (type == null)
-                {
-                    int objectId = identifiers.reverse(classID);
-                    if (objectId >= 0)
-                    {
-                        String msg = MessageUtil.format(Messages.HprofParserHandlerImpl_Error_ExpectedClassSegment,
-                                        Long.toHexString(classID));
-                        throw new SnapshotException(msg);
-                    }
-                    // Create some dummy fields
-                    int size = e.getValue();
-                    // Special value for missing superclass
-                    if (size >= Integer.MAX_VALUE)
-                        size = 0;
-                    int nfields = size / 4 + Integer.bitCount(size % 4);
-                    FieldDescriptor fds[] = new FieldDescriptor[nfields];
-                    int i;
-                    for (i = 0; i < size / 4; ++i)
-                    {
-                        fds[i] = new FieldDescriptor("unknown-field-"+i, IObject.Type.INT); //$NON-NLS-1$
-                    }
-                    if ((size & 2) != 0)
-                    {
-                        fds[i] = new FieldDescriptor("unknown-field-"+i, IObject.Type.SHORT); //$NON-NLS-1$
-                        ++i;
-                    }
-                    if ((size & 1) != 0)
-                    {
-                        fds[i] = new FieldDescriptor("unknown-field-"+i, IObject.Type.BYTE); //$NON-NLS-1$
-                        ++i;
-                    }
-                    type = new ClassImpl(classID, "unknown-class-"+clsid, jlo, 0, new Field[0], fds); //$NON-NLS-1$
-                    ++clsid;
-                    addFakeClass((ClassImpl) type, -1);
-                }
-            }
-        }
-        requiredClassIDs = null;
-
-        identifiers.sort();
-    }
-
-    private int calculateInstanceSize(ClassImpl clazz)
-    {
-        if (!clazz.isArrayType())
-        {
-            return alignUpToX(calculateSizeRecursive(clazz), objectAlign);
-        }
-        else
-        {
-            // use the referenceSize only to pass the proper ID size
-            // arrays calculate the rest themselves.
-            return refSize;
-        }
-    }
-
-	private int calculateSizeRecursive(ClassImpl clazz)
-	{
-		if (clazz.getSuperClassAddress() == 0)
-		{
-			return pointerSize + refSize;
-		}
-		ClassImpl superClass = classesByAddress.get(clazz.getSuperClassAddress());
-		int ownFieldsSize = 0;
-		for (FieldDescriptor field : clazz.getFieldDescriptors())
-			ownFieldsSize += sizeOf(field);
-
-		return alignUpToX(ownFieldsSize + calculateSizeRecursive(superClass), refSize);
-	}
-
-    private int calculateClassSize(ClassImpl clazz)
-    {
-        int staticFieldsSize = 0;
-        for (Field field : clazz.getStaticFields())
-            staticFieldsSize += sizeOf(field);
-        return alignUpToX(staticFieldsSize, objectAlign);
-    }
-
-    private int sizeOf(FieldDescriptor field)
-    {
-        int type = field.getType();
-        if (type == IObject.Type.OBJECT)
-            return refSize;
-
-        return IPrimitiveArray.ELEMENT_SIZE[type];
-    }
-
-    private int alignUpToX(int n, int x)
-    {
-        int r = n % x;
-        return r == 0 ? n : n + x - r;
-    }
-
-    private long alignUpToX(long n, int x)
-    {
-        long r = n % x;
-        return r == 0 ? n : n + x - r;
-    }
-
-    public IOne2LongIndex fillIn(IPreliminaryIndex index, IProgressListener listener) throws IOException
-    {
-        /*
-         * System classes should be marked appropriately in the HPROF file.
-         * lambda classes should not be marked as system GC roots.
-         */
-        boolean foundSystemClasses = false;
-        for (Iterator<List<XGCRootInfo>>it = gcRoots.values(); it.hasNext() && !foundSystemClasses; )
-        {
-            for (XGCRootInfo x : it.next())
-            {
-                if (x.getType() == GCRootInfo.Type.SYSTEM_CLASS)
-                {
-                    foundSystemClasses = true;
-                    break;
-                }
-            }
-        }
-        if (!foundSystemClasses)
-        {
-            // Probably not needed anymore.
-            // ensure all classes loaded by the system class loaders are marked as
-            // GCRoots
-            //
-            // For some dumps produced with jmap 1.5_xx this is not the case, and
-            // it may happen that the super classes of some classes are missing
-            // Array classes, e.g. java.lang.String[][] are not explicitly
-            // marked. They are also not marked as "system class" in the non-jmap
-            // heap dumps
-            ClassImpl[] allClasses = classesByAddress.getAllValues(new ClassImpl[0]);
-            for (ClassImpl clazz : allClasses)
-            {
-                if (clazz.getClassLoaderAddress() == 0 && !clazz.isArrayType()
-                                && !gcRoots.containsKey(clazz.getObjectAddress()))
-                {
-                    addGCRoot(clazz.getObjectAddress(), 0, GCRootInfo.Type.SYSTEM_CLASS);
-                }
-            }
-        }
-
-        // classes model
-        HashMapIntObject<ClassImpl> classesById = new HashMapIntObject<ClassImpl>(classesByAddress.size());
-        for (Iterator<ClassImpl> iter = classesByAddress.values(); iter.hasNext();)
-        {
-            ClassImpl clazz = iter.next();
-            classesById.put(clazz.getObjectId(), clazz);
-        }
-        index.setClassesById(classesById);
-
-        // Create Histogram of discarded objects
-        long discardedObjects = 0;
-        long discardedSize = 0;
-        List<UnreachableObjectsHistogram.Record> records = new ArrayList<UnreachableObjectsHistogram.Record>();
-        for(ClassImpl clazz : discardedObjectsByClass.values()) {
-            records.add(new UnreachableObjectsHistogram.Record(
-                            clazz.getName(),
-                            clazz.getObjectAddress(),
-                            clazz.getNumberOfObjects(),
-                            clazz.getTotalSize()));
-            discardedObjects += clazz.getNumberOfObjects();
-            discardedSize += clazz.getTotalSize();
-        }
-        if (discardedObjects > 0)
-        {
-            UnreachableObjectsHistogram deadObjectHistogram = new UnreachableObjectsHistogram(records);
-            info.setProperty(UnreachableObjectsHistogram.class.getName(), deadObjectHistogram);
-            listener.sendUserMessage(IProgressListener.Severity.WARNING, MessageUtil.format(
-                            Messages.HprofParserHandlerImpl_DiscardedObjects, 
-                            discardedObjects, discardedSize, discardRatio, discardPattern), null);
-        }
-
-        index.setGcRoots(map2ids(gcRoots));
-
-        HashMapIntObject<HashMapIntObject<List<XGCRootInfo>>> thread2objects2roots = new HashMapIntObject<HashMapIntObject<List<XGCRootInfo>>>();
-        for (Iterator<HashMapLongObject.Entry<HashMapLongObject<List<XGCRootInfo>>>> iter = threadAddressToLocals
-                        .entries(); iter.hasNext();)
-        {
-            HashMapLongObject.Entry<HashMapLongObject<List<XGCRootInfo>>> entry = iter.next();
-            int threadId = identifiers.reverse(entry.getKey());
-            if (threadId >= 0)
-            {
-                HashMapIntObject<List<XGCRootInfo>> objects2roots = map2ids(entry.getValue());
-                if (!objects2roots.isEmpty())
-                    thread2objects2roots.put(threadId, objects2roots);
-            }
-        }
-        index.setThread2objects2roots(thread2objects2roots);
-
-        index.setIdentifiers((new LongIndexStreamer()).writeTo(Index.IDENTIFIER.getFile(info.getPrefix() + "temp."), identifiers.iterator())); //$NON-NLS-1$);
-
-        index.setArray2size(array2size.writeTo(Index.A2SIZE.getFile(info.getPrefix() + "temp."))); //$NON-NLS-1$
-
-        index.setObject2classId(object2classId.writeTo(Index.O2CLASS.getFile(info.getPrefix() + "temp."))); //$NON-NLS-1$
-
-        index.setOutbound(outbound.flush());
-
-        return object2position.writeTo(new File(info.getPrefix() + "temp.o2hprof.index")); //$NON-NLS-1$
-    }
-
-    private HashMapIntObject<List<XGCRootInfo>> map2ids(HashMapLongObject<List<XGCRootInfo>> source)
-    {
-        HashMapIntObject<List<XGCRootInfo>> sink = new HashMapIntObject<List<XGCRootInfo>>();
-        for (Iterator<HashMapLongObject.Entry<List<XGCRootInfo>>> iter = source.entries(); iter.hasNext();)
-        {
-            HashMapLongObject.Entry<List<XGCRootInfo>> entry = iter.next();
-            int idx = identifiers.reverse(entry.getKey());
-            if (idx >= 0)
-            {
-                // sometimes it happens that there is no object for an
-                // address reported as a GC root. It's not clear why
-                for (Iterator<XGCRootInfo> roots = entry.getValue().iterator(); roots.hasNext();)
-                {
-                    XGCRootInfo root = roots.next();
-                    root.setObjectId(idx);
-                    if (root.getContextAddress() != 0)
-                    {
-                        int contextId = identifiers.reverse(root.getContextAddress());
-                        if (contextId < 0)
-                            roots.remove();
-                        else
-                            root.setContextId(contextId);
-                    }
-                }
-                sink.put(idx, entry.getValue());
-            }
-        }
-        return sink;
-    }
-
-    public void cancel()
-    {
-        if (outbound != null)
-            outbound.cancel();
-
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // report parsed entities
-    // //////////////////////////////////////////////////////////////
-
-    public void addProperty(String name, String value) throws IOException
-    {
-        if (IHprofParserHandler.VERSION.equals(name))
-        {
-            version = Version.valueOf(value);
-            info.setProperty(HprofHeapObjectReader.VERSION_PROPERTY, version.name());
-        }
-        else if (IHprofParserHandler.IDENTIFIER_SIZE.equals(name))
-        {
-            int idSize = Integer.parseInt(value);
-            info.setIdentifierSize(idSize);
-            pointerSize = idSize;
-            refSize = idSize;
-        }
-        else if (IHprofParserHandler.CREATION_DATE.equals(name))
-        {
-            info.setCreationDate(new Date(Long.parseLong(value)));
-        }
-        else if (IHprofParserHandler.REFERENCE_SIZE.equals(name))
-        {
-            refSize = Integer.parseInt(value);
-        }
-        else if (IHprofParserHandler.STREAM_LENGTH.equals(name))
-        {
-            long length = Long.parseLong(value);
-            info.setProperty(HprofHeapObjectReader.HPROF_LENGTH_PROPERTY, length);
-        }
-    }
-
-    public void addGCRoot(long id, long referrer, int rootType)
-    {
-        if (referrer != 0)
-        {
-            HashMapLongObject<List<XGCRootInfo>> localAddressToRootInfo = threadAddressToLocals.get(referrer);
-            if (localAddressToRootInfo == null)
-            {
-                localAddressToRootInfo = new HashMapLongObject<List<XGCRootInfo>>();
-                threadAddressToLocals.put(referrer, localAddressToRootInfo);
-            }
-            List<XGCRootInfo> gcRootInfo = localAddressToRootInfo.get(id);
-            if (gcRootInfo == null)
-            {
-                gcRootInfo = new ArrayList<XGCRootInfo>(1);
-                localAddressToRootInfo.put(id, gcRootInfo);
-            }
-            gcRootInfo.add(new XGCRootInfo(id, referrer, rootType));
-            return; // do not add the object as GC root
-        }
-
-        List<XGCRootInfo> r = gcRoots.get(id);
-        if (r == null)
-            gcRoots.put(id, r = new ArrayList<XGCRootInfo>(3));
-        r.add(new XGCRootInfo(id, referrer, rootType));
-    }
-
-    private void addFakeClass(ClassImpl clazz, long filePosition) throws IOException
-    {
-        this.identifiers.add(clazz.getObjectAddress());
-        this.classesByAddress.put(clazz.getObjectAddress(), clazz);
-
-        List<ClassImpl> list = classesByName.get(clazz.getName());
-        if (list == null)
-            classesByName.put(clazz.getName(), list = new ArrayList<ClassImpl>());
-        list.add(clazz);
-    }
-
-    public void addClass(ClassImpl clazz, long filePosition, int idSize, int instsize) throws IOException
-    {
-        this.identifiers.add(clazz.getObjectAddress());
-        this.classesByAddress.put(clazz.getObjectAddress(), clazz);
-
-        List<ClassImpl> list = classesByName.get(clazz.getName());
-        if (list == null)
-            classesByName.put(clazz.getName(), list = new ArrayList<ClassImpl>());
-        list.add(clazz);
-
-        if (clazz.getSuperClassAddress() != 0) {
-            // Try to calculate how big the superclass should be
-            int ownFieldsSize = 0;
-            for (FieldDescriptor field : clazz.getFieldDescriptors())
-            {
-                int type = field.getType();
-                if (type == IObject.Type.OBJECT)
-                    ownFieldsSize += idSize;
-                else
-                    ownFieldsSize += IPrimitiveArray.ELEMENT_SIZE[type];
-            }
-            int supersize = Math.max(instsize - ownFieldsSize, 0);
-            // A real size of an instance will override this
-            reportRequiredClass(clazz.getSuperClassAddress(), supersize, false);
-        }
-    }
-
-    private void prepareHeapObject(HeapObject object) throws IOException
-    {
-        if (object.isPrimitiveArray)
-        {
-            byte elementType = (byte) object.classIdOrElementType;
-            ClassImpl clazz = (ClassImpl) lookupPrimitiveArrayClassByType(elementType);
-            object.usedHeapSize = getPrimitiveArrayHeapSize(elementType, object.arraySize);
-            object.references.add(clazz.getObjectAddress());
-            object.clazz = clazz;
-        }
-
-        if (object.isObjectArray)
-        {
-            long arrayClassObjectID = object.classIdOrElementType;
-            ClassImpl arrayType = (ClassImpl) lookupClass(arrayClassObjectID);
-            if (arrayType == null)
-                throw new RuntimeException(MessageUtil.format(
-                                Messages.Pass2Parser_Error_HandlerMustCreateFakeClassForAddress,
-                                Long.toHexString(arrayClassObjectID)));
-
-            object.usedHeapSize = getObjectArrayHeapSize(arrayType, object.arraySize);
-            object.references.add(arrayType.getObjectAddress());
-            long[] ids = object.ids;
-            for (int ii = 0; ii < object.arraySize; ii++)
-            {
-                if (ids[ii] != 0)
-                    object.references.add(ids[ii]);
-            }
-            object.clazz = arrayType;
-            // References now transfered, so free some space
-            ids = null;
-            object.ids = null;
-        }
-
-        if (!object.isObjectArray && !object.isPrimitiveArray)
-        {
-            long classID = object.classIdOrElementType;
-            List<IClass> hierarchy = resolveClassHierarchy(classID);
-            ByteArrayPositionInputStream in = new ByteArrayPositionInputStream(object.instanceData, object.idSize);
-
-            ClassImpl thisClazz = (ClassImpl) hierarchy.get(0);
-
-            IClass objcl = lookupClass(object.objectAddress);
-            Field statics[] = new Field[0];
-            if (objcl instanceof ClassImpl)
-            {
-                // An INSTANCE_DUMP record for a class type
-                // This clazz is perhaps of different actual type, not java.lang.Class
-                // The true type has already been set in PassParser1 and beforePass2()
-                ClassImpl objcls = (ClassImpl) objcl;
-                statics = objcls.getStaticFields().toArray(statics);
-                // Heap size of each class type object is individual as have statics
-                object.clazz = thisClazz;
-                object.usedHeapSize = objcls.getUsedHeapSize();
-                // and extract the class references
-                object.references.addAll(objcls.getReferences());
-            }
-            else
-            {
-                object.clazz = thisClazz;
-                object.usedHeapSize = thisClazz.getHeapSizePerInstance();
-                object.references.add(thisClazz.getObjectAddress());
-            }
-
-            // extract outgoing references
-            int pos = 0;
-            for (IClass clazz : hierarchy)
-            {
-                for (FieldDescriptor field : clazz.getFieldDescriptors())
-                {
-                    int type = field.getType();
-                    // Find match for pseudo-statics
-                    Field stField = null;
-                    for (int stidx = 0; stidx < statics.length; ++stidx)
-                    {
-                        if (statics[stidx] != null && statics[stidx].getType() == type && statics[stidx].getName().equals("<"+field.getName()+">")) { //$NON-NLS-1$ //$NON-NLS-2$
-                            // Found a field
-                            stField = statics[stidx];
-                            // Don't use this twice.
-                            statics[stidx] = null;
-                            break;
-                        }
-                    }
-                    if (type == IObject.Type.OBJECT)
-                    {
-                        long refId = in.readID(object.idSize);
-                        pos += object.idSize;
-                        if (refId != 0)
-                        {
-                            object.references.add(refId);
-                            if (stField != null)
-                            {
-                                stField.setValue(new ObjectReference(null, refId));
-                            }
-                        }
-                    }
-                    else
-                    {
-                        Object value = AbstractParser.readValue(in, null, type, object.idSize);
-                        if (stField != null)
-                            stField.setValue(value);
-                    }
-                }
-            }
-
-            if (pos != object.instanceData.length)
-            {
-                boolean unknown = false;
-                for (IClass clazz : hierarchy)
-                {
-                    if (clazz.getName().startsWith("unknown-class")) //$NON-NLS-1$
-                    {
-                        unknown = true;
-                    }
-                }
-
-                // TODO get the strictness settings across from eclipse
-//                if (unknown && (strictnessPreference == HprofStrictness.STRICTNESS_WARNING || strictnessPreference == HprofStrictness.STRICTNESS_PERMISSIVE))
-//                {
-//                    //monitor.sendUserMessage(Severity.WARNING, MessageUtil.format(Messages.Pass2Parser_Error_InsufficientBytesRead, thisClazz.getName(), Long.toHexString(id), Long.toHexString(segmentStartPos), Long.toHexString(endPos), Long.toHexString(in.position())), null);
-//                }
-//                else
-//                {
-//                    throw new IOException(MessageUtil.format(Messages.Pass2Parser_Error_InsufficientBytesRead, thisClazz.getName(), Long.toHexString(id), Long.toHexString(segmentStartPos), Long.toHexString(endPos), Long.toHexString(in.position())));
-//                }
-            }
-        }
-
-    }
-    public void addObject(HeapObject object) throws IOException
-    {
-        addObject(object, false);
-    }
-
-    private void addObject(HeapObject object, boolean prepared) throws IOException
-    {
-        if (!prepared)
-            prepareHeapObject(object);
-
-        // this may be called from multiple threads
-        // so, each function called inside here needs to be threadsafe
-        // it will not do to simply synchronize here as we need
-        // better concurrency than that
-
-        int index = mapAddressToId(object.objectAddress);
-        if (index < 0)
-        {
-            // Discarded object
-            ClassImpl cls = discardedObjectsByClass.get(object.clazz.getObjectId());
-            if (cls == null)
-            {
-                cls = new ClassImpl(object.clazz.getObjectAddress(), 
-                                object.clazz.getName(),
-                                object.clazz.getSuperClassAddress(),
-                                object.clazz.getClassLoaderAddress(),
-                                new Field[0],
-                                new FieldDescriptor[0]);
-                cls.setHeapSizePerInstance(object.clazz.getHeapSizePerInstance());
-                ClassImpl clsOld = discardedObjectsByClass.putIfAbsent(object.clazz.getObjectId(), cls);
-                if (clsOld != null)
-                    cls = clsOld;
-            }
-            /*
-             * Keep count of discards
-             * @TODO consider overflow as we count discard >Integer.MAX_VALUE
-             */
-            cls.addInstance(object.usedHeapSize);
-            return;
-        }
-
-        // check if some thread to local variables references have to be added
-        HashMapLongObject<List<XGCRootInfo>> localVars = threadAddressToLocals.get(object.objectAddress);
-        if (localVars != null)
-        {
-            IteratorLong e = localVars.keys();
-            while (e.hasNext())
-            {
-                object.references.add(e.next());
-            }
-        }
-
-        // log references
-        outbound.log(identifiers, index, object.references);
-
-        int classIndex = object.clazz.getObjectId();
-        object.clazz.addInstance(object.usedHeapSize);
-
-        // log address
-        object2classId.set(index, classIndex);
-        object2position.set(index, object.filePosition);
-
-        // log array size
-        if (object.isPrimitiveArray || object.isObjectArray)
-            array2size.set(index, object.usedHeapSize);
-    }
-
-    /**
-     * Randomly choose whether to discard
-     * @return
-     */
-    private boolean discard()
-    {
-        if (discardRatio <= 0.0)
-            return false;
-        double d = rand.nextDouble();
-        double top = discardRatio + discardOffset;
-        /*
-         * Wrap around the range.
-         * [dddddddd..] 0.8,0.0
-         * [..dddddddd] 0.8,0.2
-         * [dd..dddddd] 0.8,0.4
-         */
-        return (d < top && d >= discardOffset) || d < top - 1.0;
-    }
-
-    /** 
-     * Choose to discard also based on object type
-     * 
-     * @param classId the HPROF class Id
-     * @return
-     */
-    private boolean discard(long classId)
-    {
-        if (!discard())
-            return false;
-        ClassImpl cls = lookupClass(classId);
-        if (cls != null && discardPattern.matcher(cls.getName()).matches())
-        {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Adjust the last seen file position.
-     * Used to see how many bits are needed for the index.
-     * @param filePosition
-     */
-    private void reportFilePosition(long filePosition)
-    {
-        if (filePosition > maxFilePosition)
-            maxFilePosition = filePosition;
-    }
-
-    private void reportInstance(long id, long filePosition)
-    {
-        this.identifiers.add(id);
-        reportFilePosition(filePosition);
-    }
-
-    public void reportInstanceWithClass(long id, long filePosition, long classID, int size)
-    {
-        if (discard(classID))
-        {
-            // Skip
-            reportFilePosition(filePosition);
-            return;
-        }
-        reportInstance(id, filePosition);
-        reportRequiredClass(classID, size, true);
-    }
-
-    public void reportInstanceOfObjectArray(long id, long filePosition, long arrayClassID)
-    {
-        if (discard(arrayClassID))
-        {
-            // Skip
-            reportFilePosition(filePosition);
-            return;
-        }
-        reportInstance(id, filePosition);
-        reportRequiredObjectArray(arrayClassID);
-    }
-
-    public void reportInstanceOfPrimitiveArray(long id, long filePosition, int arrayType)
-    {
-        if (discard() && discardPattern.matcher(IPrimitiveArray.TYPE[arrayType]).matches())
-        {
-            // Skip
-            reportFilePosition(filePosition);
-            reportRequiredPrimitiveArray(arrayType);
-            return;
-        }
-        reportInstance(id, filePosition);
-        reportRequiredPrimitiveArray(arrayType);
-    }
-
-    private void reportRequiredObjectArray(long arrayClassID)
-    {
-        requiredArrayClassIDs.putIfAbsent(arrayClassID, true);
-    }
-
-    private void reportRequiredPrimitiveArray(int arrayType)
-    {
-        requiredPrimitiveArrays[arrayType] = true;
-    }
-
-    private void reportRequiredClass(long classID, int size, boolean sizeKnown)
-    {
-        if (sizeKnown)
-        {
-            requiredClassIDs.put(classID, size);
-        }
-        else
-        {
-            requiredClassIDs.putIfAbsent(classID, size);
-        }
-    }
-
-    // //////////////////////////////////////////////////////////////
-    // lookup heap infos
-    // //////////////////////////////////////////////////////////////
-
-    public int getIdentifierSize()
-    {
-        return info.getIdentifierSize();
-    }
-
-    public ClassImpl lookupClass(long classId)
-    {
-        return classesByAddress.get(classId);
-    }
-
-    public IClass lookupPrimitiveArrayClassByType(byte elementType)
-    {
-        return primitiveArrays[elementType];
-    }
-
-    public IClass lookupClassByName(String name, boolean failOnMultipleInstances)
-    {
-        List<ClassImpl> list = classesByName.get(name);
-        if (list == null)
-            return null;
-        if (failOnMultipleInstances && list.size() != 1)
-            throw new RuntimeException(MessageUtil.format(
-                            Messages.HprofParserHandlerImpl_Error_MultipleClassInstancesExist, name));
-        return list.get(0);
-    }
-
-    public IClass lookupClassByIndex(int objIndex)
-    {
-        return lookupClass(this.identifiers.get(objIndex));
-    }
-
-    ConcurrentHashMap<Long, List<IClass>> classHierarchyCache = new ConcurrentHashMap<Long, List<IClass>>();
-    public List<IClass> resolveClassHierarchy(long classId)
-    {
-        List<IClass> cached = classHierarchyCache.get(classId);
-        if (cached != null)
-        {
-            return cached;
-        }
-
-        List<IClass> answer = new ArrayList<IClass>();
-
-        ClassImpl clazz = classesByAddress.get(classId);
-        answer.add(clazz);
-
-        while (clazz.hasSuperClass())
-        {
-            clazz = classesByAddress.get(clazz.getSuperClassAddress());
-            answer.add(clazz);
-        }
-
-        classHierarchyCache.put(classId, answer);
-        return answer;
-    }
-
-    public int mapAddressToId(long address)
-    {
-        return this.identifiers.reverse(address);
-    }
-
-    public XSnapshotInfo getSnapshotInfo()
-    {
-        return info;
-    }
-
-    public long getObjectArrayHeapSize(ClassImpl arrayType, int size)
-    {
-        long usedHeapSize = alignUpToX(pointerSize + refSize + 4 + size * arrayType.getHeapSizePerInstance(), objectAlign);
-        return usedHeapSize;
-    }
-
-    public long getPrimitiveArrayHeapSize(byte elementType, int size)
-    {
-        long usedHeapSize = alignUpToX(alignUpToX(pointerSize + refSize + 4, refSize) + size * (long)PrimitiveArrayImpl.ELEMENT_SIZE[(int) elementType], objectAlign);
-        return usedHeapSize;
-    }
-
-}
+/*******************************************************************************

+ * Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License 2.0

+ * which accompanies this distribution, and is available at

+ * https://www.eclipse.org/legal/epl-2.0/

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ * Contributors:

+ *    SAP AG - initial API and implementation

+ *    Andrew Johnson - bug fix for missing classes

+ *    Netflix (Jason Koch) - refactors for increased performance and concurrency

+ *******************************************************************************/

+package org.eclipse.mat.hprof;

+

+import java.io.File;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Map;

+import java.util.Random;

+import java.util.concurrent.ConcurrentHashMap;

+import java.util.regex.Pattern;

+

+import org.eclipse.mat.SnapshotException;

+import org.eclipse.mat.collect.HashMapIntObject;

+import org.eclipse.mat.collect.HashMapLongObject;

+import org.eclipse.mat.collect.HashMapLongObject.Entry;

+import org.eclipse.mat.collect.IteratorLong;

+import org.eclipse.mat.hprof.describer.Version;

+import org.eclipse.mat.hprof.ui.HprofPreferences;

+import org.eclipse.mat.parser.IPreliminaryIndex;

+import org.eclipse.mat.parser.index.IIndexReader.IOne2LongIndex;

+import org.eclipse.mat.parser.index.IndexManager.Index;

+import org.eclipse.mat.parser.index.IndexWriter;

+import org.eclipse.mat.parser.index.IndexWriter.IntArray1NWriter;

+import org.eclipse.mat.parser.index.IndexWriter.IntIndexCollector;

+import org.eclipse.mat.parser.index.IndexWriter.LongIndexCollector;

+import org.eclipse.mat.parser.index.IndexWriter.LongIndexStreamer;

+import org.eclipse.mat.parser.model.ClassImpl;

+import org.eclipse.mat.parser.model.PrimitiveArrayImpl;

+import org.eclipse.mat.parser.model.XGCRootInfo;

+import org.eclipse.mat.parser.model.XSnapshotInfo;

+import org.eclipse.mat.snapshot.UnreachableObjectsHistogram;

+import org.eclipse.mat.snapshot.model.Field;

+import org.eclipse.mat.snapshot.model.FieldDescriptor;

+import org.eclipse.mat.snapshot.model.GCRootInfo;

+import org.eclipse.mat.snapshot.model.IClass;

+import org.eclipse.mat.snapshot.model.IObject;

+import org.eclipse.mat.snapshot.model.IPrimitiveArray;

+import org.eclipse.mat.snapshot.model.ObjectReference;

+import org.eclipse.mat.util.IProgressListener;

+import org.eclipse.mat.util.MessageUtil;

+

+public class HprofParserHandlerImpl implements IHprofParserHandler

+{

+    // private String prefix;

+    private Version version;

+

+    private XSnapshotInfo info = new XSnapshotInfo();

+

+    private Map<String, List<ClassImpl>> classesByName = new HashMap<String, List<ClassImpl>>();

+    private HashMapLongObject<ClassImpl> classesByAddress = new HashMapLongObject<ClassImpl>();

+

+    private HashMapLongObject<List<XGCRootInfo>> gcRoots = new HashMapLongObject<List<XGCRootInfo>>(200);

+

+    private IndexWriter.Identifier identifiers = null;

+    private IntArray1NWriter outbound = null;

+    private IntIndexCollector object2classId = null;

+    private LongIndexCollector object2position = null;

+    private IndexWriter.SizeIndexCollectorUncompressed array2size = null;

+

+    private HashMap<Long, Boolean> requiredArrayClassIDs = new HashMap<Long, Boolean>();

+    private HashMap<Long, Integer> requiredClassIDs = new HashMap<Long, Integer>();

+    private IClass[] primitiveArrays = new IClass[IPrimitiveArray.TYPE.length];

+    private boolean[] requiredPrimitiveArrays = new boolean[IPrimitiveArray.COMPONENT_TYPE.length];

+

+    private HashMapLongObject<HashMapLongObject<List<XGCRootInfo>>> threadAddressToLocals = new HashMapLongObject<HashMapLongObject<List<XGCRootInfo>>>();

+

+    /** Keep track of numbers and size of discarded objects */

+    private ConcurrentHashMap<Integer, ClassImpl> discardedObjectsByClass = new ConcurrentHashMap<Integer, ClassImpl>();

+

+    // The size of (possibly compressed) references in the heap

+    private int refSize;

+    // The size of uncompressed pointers in the object headers in the heap

+    private int pointerSize;

+    // The alignment between successive objects

+    private int objectAlign;

+    // New size of classes including per-instance fields

+    private final boolean NEWCLASSSIZE = HprofPreferences.useAdditionalClassReferences();

+    // Largest offset into HPROF file

+    private long maxFilePosition = 0;

+

+    /** Which class instances to possibly discard */ 

+    private Pattern discardPattern = Pattern.compile("char\\[\\]|java\\.lang\\.String"); //$NON-NLS-1$

+    /** How often to discard */

+    private double discardRatio = 0.0;

+    /** Select which group of objects are discarded */

+    private double discardOffset = 0.0;

+    /** Random number seed for choosing discards */

+    private long discardSeed = 1;

+    /** Random number generator to choose what to discard */

+    private Random rand = new Random(discardSeed);

+

+    // //////////////////////////////////////////////////////////////

+    // lifecycle

+    // //////////////////////////////////////////////////////////////

+

+    public void beforePass1(XSnapshotInfo snapshotInfo) throws IOException

+    {

+        this.info = snapshotInfo;

+        this.identifiers = new IndexWriter.Identifier();

+        if (info.getProperty("discard_ratio") instanceof Integer) //$NON-NLS-1$

+        {

+            discardRatio = (Integer)info.getProperty("discard_ratio") / 100.0; //$NON-NLS-1$

+            if (info.getProperty("discard_offset") instanceof Integer) //$NON-NLS-1$

+            {

+                discardOffset = (Integer)info.getProperty("discard_offset") / 100.0; //$NON-NLS-1$

+            }

+            else

+            {

+                info.setProperty("discard_offset", (int)Math.round(discardOffset * 100)); //$NON-NLS-1$

+            }

+            if (info.getProperty("discard_seed") instanceof Integer) //$NON-NLS-1$

+            {

+                discardSeed = (Integer)info.getProperty("discard_seed"); //$NON-NLS-1$

+            }

+            else

+            {

+                info.setProperty("discard_seed", discardSeed); //$NON-NLS-1$

+            }

+            rand = new Random(discardSeed);

+            if (info.getProperty("discard_pattern") instanceof String) //$NON-NLS-1$

+            {

+                discardPattern = Pattern.compile((String)info.getProperty("discard_pattern")); //$NON-NLS-1$

+            }

+            else

+            {

+                info.setProperty("discard_pattern", discardPattern.toString()); //$NON-NLS-1$

+            }

+        }

+    }

+

+    public void beforePass2(IProgressListener monitor) throws IOException, SnapshotException

+    {

+        // add dummy address for system class loader object

+        identifiers.add(0);

+

+        // sort and assign preliminary object ids

+        identifiers.sort();

+

+        // See what the actual object alignment is

+        calculateAlignment();

+

+        // Set property to show if compressed oops are used on x64 bit dumps

+        if (pointerSize == 8) // if x64 bit dump

+        {

+            info.setProperty("$useCompressedOops", refSize == 4); //$NON-NLS-1$

+        }

+

+        // if necessary, create required classes not contained in the heap

+        createRequiredFakeClasses();

+

+        // informational messages to the user

+        monitor.sendUserMessage(IProgressListener.Severity.INFO, MessageUtil.format(

+                        Messages.HprofParserHandlerImpl_HeapContainsObjects, info.getPath(), identifiers.size()), null);

+

+        // if instance dumps for classes are present, then fix up the classes

+        addTypesAndDummyStatics();

+

+        int maxClassId = 0;

+

+        // calculate instance size for all classes

+        for (Iterator<?> e = classesByAddress.values(); e.hasNext();)

+        {

+            ClassImpl clazz = (ClassImpl) e.next();

+            int index = identifiers.reverse(clazz.getObjectAddress());

+            clazz.setObjectId(index);

+

+            maxClassId = Math.max(maxClassId, index);

+

+            clazz.setHeapSizePerInstance(calculateInstanceSize(clazz));

+            clazz.setUsedHeapSize(calculateClassSize(clazz));

+        }

+

+        // create index writers

+        outbound = new IntArray1NWriter(this.identifiers.size(), Index.OUTBOUND.getFile(info.getPrefix()

+                        + "temp."));//$NON-NLS-1$

+        object2classId = new IntIndexCollector(this.identifiers.size(), IndexWriter

+                        .mostSignificantBit(maxClassId));

+        object2position = new LongIndexCollector(this.identifiers.size(), IndexWriter

+                        .mostSignificantBit(maxFilePosition));

+        array2size = new IndexWriter.SizeIndexCollectorUncompressed(this.identifiers.size());

+

+        // java.lang.Class needs some special treatment so that object2classId

+        // is written correctly

+        List<ClassImpl> javaLangClasses = classesByName.get(ClassImpl.JAVA_LANG_CLASS);

+        ClassImpl javaLangClass = javaLangClasses.get(0);

+        javaLangClass.setObjectId(identifiers.reverse(javaLangClass.getObjectAddress()));

+

+        // log references for classes

+        for (Iterator<?> e = classesByAddress.values(); e.hasNext();)

+        {

+            ClassImpl clazz = (ClassImpl) e.next();

+            clazz.setSuperClassIndex(identifiers.reverse(clazz.getSuperClassAddress()));

+            clazz.setClassLoaderIndex(identifiers.reverse(clazz.getClassLoaderAddress()));

+

+            // [INFO] in newer jdk hprof files, the boot class loader

+            // has an address other than 0. The class loader instances

+            // is still not contained in the hprof file

+            if (clazz.getClassLoaderId() < 0)

+            {

+                clazz.setClassLoaderAddress(0);

+                clazz.setClassLoaderIndex(identifiers.reverse(0));

+            }

+

+            boolean skipLogRefs = false;

+            // add class instance - if not set by pass1 from an instance_dump for the class

+            if (clazz.getClazz() == null)

+            {

+                clazz.setClassInstance(javaLangClass);

+                if (NEWCLASSSIZE)

+                {

+                    // Recalculate the clazz heap size based on also java.lang.Class fields

+                    // No need to do this for classes of other types as

+                    // these have object instance records with fields converted to statics

+                    clazz.setUsedHeapSize(clazz.getUsedHeapSize() + clazz.getClazz().getHeapSizePerInstance());

+                }

+                clazz.getClazz().addInstance(clazz.getUsedHeapSize());

+            }

+            else

+            {

+                // References for classes with instance dump records will be generated in pass2

+                skipLogRefs = true;

+            }

+

+            // resolve super class

+            ClassImpl superclass = lookupClass(clazz.getSuperClassAddress());

+            if (superclass != null)

+                superclass.addSubClass(clazz);

+

+            object2classId.set(clazz.getObjectId(), clazz.getClazz().getObjectId());

+

+            if (!skipLogRefs)

+                outbound.log(identifiers, clazz.getObjectId(), clazz.getReferences());

+        }

+

+        // report dependencies for system class loader

+        // (if no classes use this class loader, cleanup garbage will remove it

+        // again)

+        ClassImpl classLoaderClass = this.classesByName.get(IClass.JAVA_LANG_CLASSLOADER).get(0);

+        HeapObject heapObject = new HeapObject(0, classLoaderClass, classLoaderClass

+                        .getHeapSizePerInstance());

+        heapObject.references.add(classLoaderClass.getObjectAddress());

+        this.addObject(heapObject, true);

+

+    }

+

+    /**

+     * Possible HPROF extension:

+     * classes also with instance dump records.

+     * The classes could be of a type other than java.lang.Class

+     * There could also be per-instance fields defined by their type.

+     * Those values can be made accessible to MAT by creating pseudo-static fields.

+     */

+    private void addTypesAndDummyStatics()

+    {

+        // Set type (and size?) for classes with object instances

+        // These will have had the type set by Pass1Parser

+        for (Iterator<Entry<ClassImpl>> it = classesByAddress.entries(); it.hasNext();)

+        {

+            Entry<ClassImpl> e = it.next();

+            ClassImpl cl = e.getValue();

+            ClassImpl type = cl.getClazz();

+            if (type != null)

+            {

+                List<FieldDescriptor> newStatics = new ArrayList<FieldDescriptor>(cl.getStaticFields());

+                List<IClass> icls = resolveClassHierarchy(type.getClassAddress());

+                for (IClass tcl : icls)

+                {

+                    for (FieldDescriptor fd : tcl.getFieldDescriptors())

+                    {

+                        // Create pseudo-static field

+                        Field st = new Field("<" + fd.getName() + ">", fd.getType(), null); //$NON-NLS-1$ //$NON-NLS-2$

+                        newStatics.add(st);

+                    }

+                }

+                if (newStatics.size() != cl.getStaticFields().size())

+                {

+                    ClassImpl newcl = new ClassImpl(cl.getObjectAddress(), cl.getName(), cl.getSuperClassAddress(),

+                                    cl.getClassLoaderAddress(), newStatics.toArray(new Field[newStatics.size()]),

+                                    cl.getFieldDescriptors().toArray(new FieldDescriptor[0]));

+                    newcl.setClassInstance(type);

+                    // Fix up the existing lookups

+                    classesByAddress.put(e.getKey(), newcl);

+                    List<ClassImpl>nms = classesByName.get(cl.getName());

+                    for (int i = 0; i < nms.size(); ++i)

+                    {

+                        if (nms.get(i) == cl)

+                            nms.set(i, newcl);

+                    }

+                }

+            }

+        }

+

+        // Set type to new type with the dummy static fields

+        for (Iterator<Entry<ClassImpl>> it = classesByAddress.entries(); it.hasNext();)

+        {

+            Entry<ClassImpl> e = it.next();

+            ClassImpl cl = e.getValue();

+            ClassImpl type = cl.getClazz();

+            if (type != null)

+            {

+                ClassImpl type2 = classesByAddress.get(type.getObjectAddress());

+                // Actual object test, not equality as we need to maintain linkage to the new class

+                if (type != type2)

+                {

+                    cl.setClassInstance(type2);

+                }

+            }

+        }

+    }

+

+    /**

+     * Calculate possible restrictions on object alignment by finding the GCD of differences

+     * between object addresses (ignoring address 0).

+     */

+    private void calculateAlignment()

+    {

+        // Minimum alignment of 8 bytes

+        final int minAlign = 8;

+        // Maximum alignment of 256 bytes

+        final int maxAlign = 256;

+        long prev = 0;

+        long align = 0;

+        for (IteratorLong it = identifiers.iterator(); it.hasNext(); )

+        {

+            long next = it.next();

+            if (next == 0)

+                continue;

+            long diff = next - prev;

+            prev = next;

+            if (next == diff)

+                continue;

+            if (align == 0)

+            {

+                align = diff;

+            }

+            else

+            {

+                long mx = Math.max(align, diff);

+                long mn = Math.min(align, diff);

+                long d = mx % mn;

+                while (d != 0) {

+                    mx = mn;

+                    mn = d;

+                    d = mx % mn;

+                }

+                align = mn;

+                // Minimum alignment

+                if (align <= minAlign)

+                    break;

+            }

+        }

+        // Sanitise the alignment

+        objectAlign = Math.max((int)Math.min(align, maxAlign), minAlign);

+    }

+

+    private void createRequiredFakeClasses() throws IOException, SnapshotException

+    {

+        // we know: system class loader has object address 0

+        long nextObjectAddress = 0;

+        // For generating the fake class names

+        int clsid = 0;

+        // java.lang.Object for the superclass

+        List<ClassImpl>jlos = classesByName.get("java.lang.Object"); //$NON-NLS-1$

+        long jlo = jlos == null || jlos.isEmpty() ? 0 : jlos.get(0).getObjectAddress();

+        // Fake java.lang.Class, java.lang.ClassLoader for later

+        String clss[] = {ClassImpl.JAVA_LANG_CLASS, ClassImpl.JAVA_LANG_CLASSLOADER};

+        for (String cls : clss)

+        {

+            List<ClassImpl>jlcs = classesByName.get(cls);

+            if (jlcs == null || jlcs.isEmpty())

+            {

+                while (identifiers.reverse(++nextObjectAddress) >= 0)

+                {}

+                IClass type = new ClassImpl(nextObjectAddress, cls, jlo, 0, new Field[0], new FieldDescriptor[0]);

+                addFakeClass((ClassImpl) type, -1);

+            }

+        }

+

+        // create required (fake) classes for arrays

+        if (!requiredArrayClassIDs.isEmpty())

+        {

+            for (long arrayClassID : requiredArrayClassIDs.keySet())

+            {

+                IClass arrayType = lookupClass(arrayClassID);

+                if (arrayType == null)

+                {

+                    int objectId = identifiers.reverse(arrayClassID);

+                    if (objectId >= 0)

+                    {

+                        String msg = MessageUtil.format(Messages.HprofParserHandlerImpl_Error_ExpectedClassSegment,

+                                        Long.toHexString(arrayClassID));

+                        throw new SnapshotException(msg);

+                    }

+

+                    arrayType = new ClassImpl(arrayClassID, "unknown-class-"+clsid+"[]", jlo, 0, new Field[0], //$NON-NLS-1$ //$NON-NLS-2$

+                                    new FieldDescriptor[0]);

+                    ++clsid;

+                    addFakeClass((ClassImpl) arrayType, -1);

+                }

+            }

+        }

+        requiredArrayClassIDs = null;

+

+        for(int arrayType = 0; arrayType < requiredPrimitiveArrays.length; arrayType++)

+        {

+            if (requiredPrimitiveArrays[arrayType])

+            {

+                String name = IPrimitiveArray.TYPE[arrayType];

+                IClass clazz = lookupClassByName(name, true);

+                if (clazz == null)

+                {

+                    while (identifiers.reverse(++nextObjectAddress) >= 0)

+                    {}

+

+                    clazz = new ClassImpl(nextObjectAddress, name, jlo, 0, new Field[0], new FieldDescriptor[0]);

+                    addFakeClass((ClassImpl) clazz, -1);

+                }

+                primitiveArrays[arrayType] = clazz;

+            }

+        }

+

+        // create required (fake) classes for objects

+        if (!requiredClassIDs.isEmpty())

+        {

+            for (Map.Entry<Long, Integer> e : requiredClassIDs.entrySet())

+            {

+                long classID = e.getKey();

+                IClass type = lookupClass(classID);

+                if (type == null)

+                {

+                    int objectId = identifiers.reverse(classID);

+                    if (objectId >= 0)

+                    {

+                        String msg = MessageUtil.format(Messages.HprofParserHandlerImpl_Error_ExpectedClassSegment,

+                                        Long.toHexString(classID));

+                        throw new SnapshotException(msg);

+                    }

+                    // Create some dummy fields

+                    int size = e.getValue();

+                    // Special value for missing superclass

+                    if (size >= Integer.MAX_VALUE)

+                        size = 0;

+                    int nfields = size / 4 + Integer.bitCount(size % 4);

+                    FieldDescriptor fds[] = new FieldDescriptor[nfields];

+                    int i;

+                    for (i = 0; i < size / 4; ++i)

+                    {

+                        fds[i] = new FieldDescriptor("unknown-field-"+i, IObject.Type.INT); //$NON-NLS-1$

+                    }

+                    if ((size & 2) != 0)

+                    {

+                        fds[i] = new FieldDescriptor("unknown-field-"+i, IObject.Type.SHORT); //$NON-NLS-1$

+                        ++i;

+                    }

+                    if ((size & 1) != 0)

+                    {

+                        fds[i] = new FieldDescriptor("unknown-field-"+i, IObject.Type.BYTE); //$NON-NLS-1$

+                        ++i;

+                    }

+                    type = new ClassImpl(classID, "unknown-class-"+clsid, jlo, 0, new Field[0], fds); //$NON-NLS-1$

+                    ++clsid;

+                    addFakeClass((ClassImpl) type, -1);

+                }

+            }

+        }

+        requiredClassIDs = null;

+

+        identifiers.sort();

+    }

+

+    private int calculateInstanceSize(ClassImpl clazz)

+    {

+        if (!clazz.isArrayType())

+        {

+            return alignUpToX(calculateSizeRecursive(clazz), objectAlign);

+        }

+        else

+        {

+            // use the referenceSize only to pass the proper ID size

+            // arrays calculate the rest themselves.

+            return refSize;

+        }

+    }

+

+	private int calculateSizeRecursive(ClassImpl clazz)

+	{

+		if (clazz.getSuperClassAddress() == 0)

+		{

+			return pointerSize + refSize;

+		}

+		ClassImpl superClass = classesByAddress.get(clazz.getSuperClassAddress());

+		int ownFieldsSize = 0;

+		for (FieldDescriptor field : clazz.getFieldDescriptors())

+			ownFieldsSize += sizeOf(field);

+

+		return alignUpToX(ownFieldsSize + calculateSizeRecursive(superClass), refSize);

+	}

+

+    private int calculateClassSize(ClassImpl clazz)

+    {

+        int staticFieldsSize = 0;

+        for (Field field : clazz.getStaticFields())

+            staticFieldsSize += sizeOf(field);

+        return alignUpToX(staticFieldsSize, objectAlign);

+    }

+

+    private int sizeOf(FieldDescriptor field)

+    {

+        int type = field.getType();

+        if (type == IObject.Type.OBJECT)

+            return refSize;

+

+        return IPrimitiveArray.ELEMENT_SIZE[type];

+    }

+

+    private int alignUpToX(int n, int x)

+    {

+        int r = n % x;

+        return r == 0 ? n : n + x - r;

+    }

+

+    private long alignUpToX(long n, int x)

+    {

+        long r = n % x;

+        return r == 0 ? n : n + x - r;

+    }

+

+    public IOne2LongIndex fillIn(IPreliminaryIndex index, IProgressListener listener) throws IOException

+    {

+        /*

+         * System classes should be marked appropriately in the HPROF file.

+         * lambda classes should not be marked as system GC roots.

+         */

+        boolean foundSystemClasses = false;

+        for (Iterator<List<XGCRootInfo>>it = gcRoots.values(); it.hasNext() && !foundSystemClasses; )

+        {

+            for (XGCRootInfo x : it.next())

+            {

+                if (x.getType() == GCRootInfo.Type.SYSTEM_CLASS)

+                {

+                    foundSystemClasses = true;

+                    break;

+                }

+            }

+        }

+        if (!foundSystemClasses)

+        {

+            // Probably not needed anymore.

+            // ensure all classes loaded by the system class loaders are marked as

+            // GCRoots

+            //

+            // For some dumps produced with jmap 1.5_xx this is not the case, and

+            // it may happen that the super classes of some classes are missing

+            // Array classes, e.g. java.lang.String[][] are not explicitly

+            // marked. They are also not marked as "system class" in the non-jmap

+            // heap dumps

+            ClassImpl[] allClasses = classesByAddress.getAllValues(new ClassImpl[0]);

+            for (ClassImpl clazz : allClasses)

+            {

+                if (clazz.getClassLoaderAddress() == 0 && !clazz.isArrayType()

+                                && !gcRoots.containsKey(clazz.getObjectAddress()))

+                {

+                    addGCRoot(clazz.getObjectAddress(), 0, GCRootInfo.Type.SYSTEM_CLASS);

+                }

+            }

+        }

+

+        // classes model

+        HashMapIntObject<ClassImpl> classesById = new HashMapIntObject<ClassImpl>(classesByAddress.size());

+        for (Iterator<ClassImpl> iter = classesByAddress.values(); iter.hasNext();)

+        {

+            ClassImpl clazz = iter.next();

+            classesById.put(clazz.getObjectId(), clazz);

+        }

+        index.setClassesById(classesById);

+

+        // Create Histogram of discarded objects

+        long discardedObjects = 0;

+        long discardedSize = 0;

+        List<UnreachableObjectsHistogram.Record> records = new ArrayList<UnreachableObjectsHistogram.Record>();

+        for(ClassImpl clazz : discardedObjectsByClass.values()) {

+            records.add(new UnreachableObjectsHistogram.Record(

+                            clazz.getName(),

+                            clazz.getObjectAddress(),

+                            clazz.getNumberOfObjects(),

+                            clazz.getTotalSize()));

+            discardedObjects += clazz.getNumberOfObjects();

+            discardedSize += clazz.getTotalSize();

+        }

+        if (discardedObjects > 0)

+        {

+            UnreachableObjectsHistogram deadObjectHistogram = new UnreachableObjectsHistogram(records);

+            info.setProperty(UnreachableObjectsHistogram.class.getName(), deadObjectHistogram);

+            listener.sendUserMessage(IProgressListener.Severity.WARNING, MessageUtil.format(

+                            Messages.HprofParserHandlerImpl_DiscardedObjects, 

+                            discardedObjects, discardedSize, discardRatio, discardPattern), null);

+        }

+

+        index.setGcRoots(map2ids(gcRoots));

+

+        HashMapIntObject<HashMapIntObject<List<XGCRootInfo>>> thread2objects2roots = new HashMapIntObject<HashMapIntObject<List<XGCRootInfo>>>();

+        for (Iterator<HashMapLongObject.Entry<HashMapLongObject<List<XGCRootInfo>>>> iter = threadAddressToLocals

+                        .entries(); iter.hasNext();)

+        {

+            HashMapLongObject.Entry<HashMapLongObject<List<XGCRootInfo>>> entry = iter.next();

+            int threadId = identifiers.reverse(entry.getKey());

+            if (threadId >= 0)

+            {

+                HashMapIntObject<List<XGCRootInfo>> objects2roots = map2ids(entry.getValue());

+                if (!objects2roots.isEmpty())

+                    thread2objects2roots.put(threadId, objects2roots);

+            }

+        }

+        index.setThread2objects2roots(thread2objects2roots);

+

+        index.setIdentifiers((new LongIndexStreamer()).writeTo(Index.IDENTIFIER.getFile(info.getPrefix() + "temp."), identifiers.iterator())); //$NON-NLS-1$);

+

+        index.setArray2size(array2size.writeTo(Index.A2SIZE.getFile(info.getPrefix() + "temp."))); //$NON-NLS-1$

+

+        index.setObject2classId(object2classId.writeTo(Index.O2CLASS.getFile(info.getPrefix() + "temp."))); //$NON-NLS-1$

+

+        index.setOutbound(outbound.flush());

+

+        return object2position.writeTo(new File(info.getPrefix() + "temp.o2hprof.index")); //$NON-NLS-1$

+    }

+

+    private HashMapIntObject<List<XGCRootInfo>> map2ids(HashMapLongObject<List<XGCRootInfo>> source)

+    {

+        HashMapIntObject<List<XGCRootInfo>> sink = new HashMapIntObject<List<XGCRootInfo>>();

+        for (Iterator<HashMapLongObject.Entry<List<XGCRootInfo>>> iter = source.entries(); iter.hasNext();)

+        {

+            HashMapLongObject.Entry<List<XGCRootInfo>> entry = iter.next();

+            int idx = identifiers.reverse(entry.getKey());

+            if (idx >= 0)

+            {

+                // sometimes it happens that there is no object for an

+                // address reported as a GC root. It's not clear why

+                for (Iterator<XGCRootInfo> roots = entry.getValue().iterator(); roots.hasNext();)

+                {

+                    XGCRootInfo root = roots.next();

+                    root.setObjectId(idx);

+                    if (root.getContextAddress() != 0)

+                    {

+                        int contextId = identifiers.reverse(root.getContextAddress());

+                        if (contextId < 0)

+                            roots.remove();

+                        else

+                            root.setContextId(contextId);

+                    }

+                }

+                sink.put(idx, entry.getValue());

+            }

+        }

+        return sink;

+    }

+

+    public void cancel()

+    {

+        if (outbound != null)

+            outbound.cancel();

+

+    }

+

+    // //////////////////////////////////////////////////////////////

+    // report parsed entities

+    // //////////////////////////////////////////////////////////////

+

+    public void addProperty(String name, String value) throws IOException

+    {

+        if (IHprofParserHandler.VERSION.equals(name))

+        {

+            version = Version.valueOf(value);

+            info.setProperty(HprofHeapObjectReader.VERSION_PROPERTY, version.name());

+        }

+        else if (IHprofParserHandler.IDENTIFIER_SIZE.equals(name))

+        {

+            int idSize = Integer.parseInt(value);

+            info.setIdentifierSize(idSize);

+            pointerSize = idSize;

+            refSize = idSize;

+        }

+        else if (IHprofParserHandler.CREATION_DATE.equals(name))

+        {

+            info.setCreationDate(new Date(Long.parseLong(value)));

+        }

+        else if (IHprofParserHandler.REFERENCE_SIZE.equals(name))

+        {

+            refSize = Integer.parseInt(value);

+        }

+        else if (IHprofParserHandler.STREAM_LENGTH.equals(name))

+        {

+            long length = Long.parseLong(value);

+            info.setProperty(HprofHeapObjectReader.HPROF_LENGTH_PROPERTY, length);

+        }

+        else if (IHprofParserHandler.HEAP_POSITION.equals(name))

+        {

+            long pos = Long.parseLong(value);

+            info.setProperty(HprofHeapObjectReader.HPROF_HEAP_START, pos);

+        }

+    }

+

+    public void addGCRoot(long id, long referrer, int rootType)

+    {

+        if (referrer != 0)

+        {

+            HashMapLongObject<List<XGCRootInfo>> localAddressToRootInfo = threadAddressToLocals.get(referrer);

+            if (localAddressToRootInfo == null)

+            {

+                localAddressToRootInfo = new HashMapLongObject<List<XGCRootInfo>>();

+                threadAddressToLocals.put(referrer, localAddressToRootInfo);

+            }

+            List<XGCRootInfo> gcRootInfo = localAddressToRootInfo.get(id);

+            if (gcRootInfo == null)

+            {

+                gcRootInfo = new ArrayList<XGCRootInfo>(1);

+                localAddressToRootInfo.put(id, gcRootInfo);

+            }

+            gcRootInfo.add(new XGCRootInfo(id, referrer, rootType));

+            return; // do not add the object as GC root

+        }

+

+        List<XGCRootInfo> r = gcRoots.get(id);

+        if (r == null)

+            gcRoots.put(id, r = new ArrayList<XGCRootInfo>(3));

+        r.add(new XGCRootInfo(id, referrer, rootType));

+    }

+

+    private void addFakeClass(ClassImpl clazz, long filePosition) throws IOException

+    {

+        this.identifiers.add(clazz.getObjectAddress());

+        this.classesByAddress.put(clazz.getObjectAddress(), clazz);

+

+        List<ClassImpl> list = classesByName.get(clazz.getName());

+        if (list == null)

+            classesByName.put(clazz.getName(), list = new ArrayList<ClassImpl>());

+        list.add(clazz);

+    }

+

+    public void addClass(ClassImpl clazz, long filePosition, int idSize, int instsize) throws IOException

+    {

+        this.identifiers.add(clazz.getObjectAddress());

+        this.classesByAddress.put(clazz.getObjectAddress(), clazz);

+

+        List<ClassImpl> list = classesByName.get(clazz.getName());

+        if (list == null)

+            classesByName.put(clazz.getName(), list = new ArrayList<ClassImpl>());

+        list.add(clazz);

+

+        if (clazz.getSuperClassAddress() != 0) {

+            // Try to calculate how big the superclass should be

+            int ownFieldsSize = 0;

+            for (FieldDescriptor field : clazz.getFieldDescriptors())

+            {

+                int type = field.getType();

+                if (type == IObject.Type.OBJECT)

+                    ownFieldsSize += idSize;

+                else

+                    ownFieldsSize += IPrimitiveArray.ELEMENT_SIZE[type];

+            }

+            int supersize = Math.max(instsize - ownFieldsSize, 0);

+            // A real size of an instance will override this

+            reportRequiredClass(clazz.getSuperClassAddress(), supersize, false);

+        }

+    }

+

+    private void prepareHeapObject(HeapObject object) throws IOException

+    {

+        if (object.isPrimitiveArray)

+        {

+            byte elementType = (byte) object.classIdOrElementType;

+            ClassImpl clazz = (ClassImpl) lookupPrimitiveArrayClassByType(elementType);

+            object.usedHeapSize = getPrimitiveArrayHeapSize(elementType, object.arraySize);

+            object.references.add(clazz.getObjectAddress());

+            object.clazz = clazz;

+        }

+

+        if (object.isObjectArray)

+        {

+            long arrayClassObjectID = object.classIdOrElementType;

+            ClassImpl arrayType = (ClassImpl) lookupClass(arrayClassObjectID);

+            if (arrayType == null)

+                throw new RuntimeException(MessageUtil.format(

+                                Messages.Pass2Parser_Error_HandlerMustCreateFakeClassForAddress,

+                                Long.toHexString(arrayClassObjectID)));

+

+            object.usedHeapSize = getObjectArrayHeapSize(arrayType, object.arraySize);

+            object.references.add(arrayType.getObjectAddress());

+            long[] ids = object.ids;

+            for (int ii = 0; ii < object.arraySize; ii++)

+            {

+                if (ids[ii] != 0)

+                    object.references.add(ids[ii]);

+            }

+            object.clazz = arrayType;

+            // References now transfered, so free some space

+            ids = null;

+            object.ids = null;

+        }

+

+        if (!object.isObjectArray && !object.isPrimitiveArray)

+        {

+            long classID = object.classIdOrElementType;

+            List<IClass> hierarchy = resolveClassHierarchy(classID);

+            ByteArrayPositionInputStream in = new ByteArrayPositionInputStream(object.instanceData, object.idSize);

+

+            ClassImpl thisClazz = (ClassImpl) hierarchy.get(0);

+

+            IClass objcl = lookupClass(object.objectAddress);

+            Field statics[] = new Field[0];

+            if (objcl instanceof ClassImpl)

+            {

+                // An INSTANCE_DUMP record for a class type

+                // This clazz is perhaps of different actual type, not java.lang.Class

+                // The true type has already been set in PassParser1 and beforePass2()

+                ClassImpl objcls = (ClassImpl) objcl;

+                statics = objcls.getStaticFields().toArray(statics);

+                // Heap size of each class type object is individual as have statics

+                object.clazz = thisClazz;

+                object.usedHeapSize = objcls.getUsedHeapSize();

+                // and extract the class references

+                object.references.addAll(objcls.getReferences());

+            }

+            else

+            {

+                object.clazz = thisClazz;

+                object.usedHeapSize = thisClazz.getHeapSizePerInstance();

+                object.references.add(thisClazz.getObjectAddress());

+            }

+

+            // extract outgoing references

+            int pos = 0;

+            for (IClass clazz : hierarchy)

+            {

+                for (FieldDescriptor field : clazz.getFieldDescriptors())

+                {

+                    int type = field.getType();

+                    // Find match for pseudo-statics

+                    Field stField = null;

+                    for (int stidx = 0; stidx < statics.length; ++stidx)

+                    {

+                        if (statics[stidx] != null && statics[stidx].getType() == type && statics[stidx].getName().equals("<"+field.getName()+">")) { //$NON-NLS-1$ //$NON-NLS-2$

+                            // Found a field

+                            stField = statics[stidx];

+                            // Don't use this twice.

+                            statics[stidx] = null;

+                            break;

+                        }

+                    }

+                    if (type == IObject.Type.OBJECT)

+                    {

+                        long refId = in.readID(object.idSize);

+                        pos += object.idSize;

+                        if (refId != 0)

+                        {

+                            object.references.add(refId);

+                            if (stField != null)

+                            {

+                                stField.setValue(new ObjectReference(null, refId));

+                            }

+                        }

+                    }

+                    else

+                    {

+                        Object value = AbstractParser.readValue(in, null, type, object.idSize);

+                        if (stField != null)

+                            stField.setValue(value);

+                    }

+                }

+            }

+

+            if (pos != object.instanceData.length)

+            {

+                boolean unknown = false;

+                for (IClass clazz : hierarchy)

+                {

+                    if (clazz.getName().startsWith("unknown-class")) //$NON-NLS-1$

+                    {

+                        unknown = true;

+                    }

+                }

+

+                // TODO get the strictness settings across from eclipse

+//                if (unknown && (strictnessPreference == HprofStrictness.STRICTNESS_WARNING || strictnessPreference == HprofStrictness.STRICTNESS_PERMISSIVE))

+//                {

+//                    //monitor.sendUserMessage(Severity.WARNING, MessageUtil.format(Messages.Pass2Parser_Error_InsufficientBytesRead, thisClazz.getName(), Long.toHexString(id), Long.toHexString(segmentStartPos), Long.toHexString(endPos), Long.toHexString(in.position())), null);

+//                }

+//                else

+//                {

+//                    throw new IOException(MessageUtil.format(Messages.Pass2Parser_Error_InsufficientBytesRead, thisClazz.getName(), Long.toHexString(id), Long.toHexString(segmentStartPos), Long.toHexString(endPos), Long.toHexString(in.position())));

+//                }

+            }

+        }

+

+    }

+    public void addObject(HeapObject object) throws IOException

+    {

+        addObject(object, false);

+    }

+

+    private void addObject(HeapObject object, boolean prepared) throws IOException

+    {

+        if (!prepared)

+            prepareHeapObject(object);

+

+        // this may be called from multiple threads

+        // so, each function called inside here needs to be threadsafe

+        // it will not do to simply synchronize here as we need

+        // better concurrency than that

+

+        int index = mapAddressToId(object.objectAddress);

+        if (index < 0)

+        {

+            // Discarded object

+            ClassImpl cls = discardedObjectsByClass.get(object.clazz.getObjectId());

+            if (cls == null)

+            {

+                cls = new ClassImpl(object.clazz.getObjectAddress(), 

+                                object.clazz.getName(),

+                                object.clazz.getSuperClassAddress(),

+                                object.clazz.getClassLoaderAddress(),

+                                new Field[0],

+                                new FieldDescriptor[0]);

+                cls.setHeapSizePerInstance(object.clazz.getHeapSizePerInstance());

+                ClassImpl clsOld = discardedObjectsByClass.putIfAbsent(object.clazz.getObjectId(), cls);

+                if (clsOld != null)

+                    cls = clsOld;

+            }

+            /*

+             * Keep count of discards

+             * @TODO consider overflow as we count discard >Integer.MAX_VALUE

+             */

+            cls.addInstance(object.usedHeapSize);

+            return;

+        }

+

+        // check if some thread to local variables references have to be added

+        HashMapLongObject<List<XGCRootInfo>> localVars = threadAddressToLocals.get(object.objectAddress);

+        if (localVars != null)

+        {

+            IteratorLong e = localVars.keys();

+            while (e.hasNext())

+            {

+                object.references.add(e.next());

+            }

+        }

+

+        // log references

+        outbound.log(identifiers, index, object.references);

+

+        int classIndex = object.clazz.getObjectId();

+        object.clazz.addInstance(object.usedHeapSize);

+

+        // log address

+        object2classId.set(index, classIndex);

+        object2position.set(index, object.filePosition);

+

+        // log array size

+        if (object.isPrimitiveArray || object.isObjectArray)

+            array2size.set(index, object.usedHeapSize);

+    }

+

+    /**

+     * Randomly choose whether to discard

+     * @return

+     */

+    private boolean discard()

+    {

+        if (discardRatio <= 0.0)

+            return false;

+        // Always accept the first object to give a start to the heap

+        if (identifiers.size() == 0)

+            return false;

+        double d = rand.nextDouble();

+        double top = discardRatio + discardOffset;

+        /*

+         * Wrap around the range.

+         * [dddddddd..] 0.8,0.0

+         * [..dddddddd] 0.8,0.2

+         * [dd..dddddd] 0.8,0.4

+         */

+        return (d < top && d >= discardOffset) || d < top - 1.0;

+    }

+

+    /** 

+     * Choose to discard also based on object type

+     * 

+     * @param classId the HPROF class Id

+     * @return

+     */

+    private boolean discard(long classId)

+    {

+        if (!discard())

+            return false;

+        ClassImpl cls = lookupClass(classId);

+        if (cls != null && discardPattern.matcher(cls.getName()).matches())

+        {

+            return true;

+        }

+        return false;

+    }

+

+    /**

+     * Adjust the last seen file position.

+     * Used to see how many bits are needed for the index.

+     * @param filePosition

+     */

+    private void reportFilePosition(long filePosition)

+    {

+        if (filePosition > maxFilePosition)

+            maxFilePosition = filePosition;

+    }

+

+    private void reportInstance(long id, long filePosition)

+    {

+        this.identifiers.add(id);

+        reportFilePosition(filePosition);

+    }

+

+    public void reportInstanceWithClass(long id, long filePosition, long classID, int size)

+    {

+        if (discard(classID))

+        {

+            // Skip

+            reportFilePosition(filePosition);

+            return;

+        }

+        reportInstance(id, filePosition);

+        reportRequiredClass(classID, size, true);

+    }

+

+    public void reportInstanceOfObjectArray(long id, long filePosition, long arrayClassID)

+    {

+        if (discard(arrayClassID))

+        {

+            // Skip

+            reportFilePosition(filePosition);

+            return;

+        }

+        reportInstance(id, filePosition);

+        reportRequiredObjectArray(arrayClassID);

+    }

+

+    public void reportInstanceOfPrimitiveArray(long id, long filePosition, int arrayType)

+    {

+        if (discard() && discardPattern.matcher(IPrimitiveArray.TYPE[arrayType]).matches())

+        {

+            // Skip

+            reportFilePosition(filePosition);

+            reportRequiredPrimitiveArray(arrayType);

+            return;

+        }

+        reportInstance(id, filePosition);

+        reportRequiredPrimitiveArray(arrayType);

+    }

+

+    private void reportRequiredObjectArray(long arrayClassID)

+    {

+        requiredArrayClassIDs.putIfAbsent(arrayClassID, true);

+    }

+

+    private void reportRequiredPrimitiveArray(int arrayType)

+    {

+        requiredPrimitiveArrays[arrayType] = true;

+    }

+

+    private void reportRequiredClass(long classID, int size, boolean sizeKnown)

+    {

+        if (sizeKnown)

+        {

+            requiredClassIDs.put(classID, size);

+        }

+        else

+        {

+            requiredClassIDs.putIfAbsent(classID, size);

+        }

+    }

+

+    // //////////////////////////////////////////////////////////////

+    // lookup heap infos

+    // //////////////////////////////////////////////////////////////

+

+    public int getIdentifierSize()

+    {

+        return info.getIdentifierSize();

+    }

+

+    public ClassImpl lookupClass(long classId)

+    {

+        return classesByAddress.get(classId);

+    }

+

+    public IClass lookupPrimitiveArrayClassByType(byte elementType)

+    {

+        return primitiveArrays[elementType];

+    }

+

+    public IClass lookupClassByName(String name, boolean failOnMultipleInstances)

+    {

+        List<ClassImpl> list = classesByName.get(name);

+        if (list == null)

+            return null;

+        if (failOnMultipleInstances && list.size() != 1)

+            throw new RuntimeException(MessageUtil.format(

+                            Messages.HprofParserHandlerImpl_Error_MultipleClassInstancesExist, name));

+        return list.get(0);

+    }

+

+    public IClass lookupClassByIndex(int objIndex)

+    {

+        return lookupClass(this.identifiers.get(objIndex));

+    }

+

+    ConcurrentHashMap<Long, List<IClass>> classHierarchyCache = new ConcurrentHashMap<Long, List<IClass>>();

+    public List<IClass> resolveClassHierarchy(long classId)

+    {

+        List<IClass> cached = classHierarchyCache.get(classId);

+        if (cached != null)

+        {

+            return cached;

+        }

+

+        List<IClass> answer = new ArrayList<IClass>();

+

+        ClassImpl clazz = classesByAddress.get(classId);

+        answer.add(clazz);

+

+        while (clazz.hasSuperClass())

+        {

+            clazz = classesByAddress.get(clazz.getSuperClassAddress());

+            answer.add(clazz);

+        }

+

+        classHierarchyCache.put(classId, answer);

+        return answer;

+    }

+

+    public int mapAddressToId(long address)

+    {

+        return this.identifiers.reverse(address);

+    }

+

+    public XSnapshotInfo getSnapshotInfo()

+    {

+        return info;

+    }

+

+    public long getObjectArrayHeapSize(ClassImpl arrayType, int size)

+    {

+        long usedHeapSize = alignUpToX(pointerSize + refSize + 4 + size * arrayType.getHeapSizePerInstance(), objectAlign);

+        return usedHeapSize;

+    }

+

+    public long getPrimitiveArrayHeapSize(byte elementType, int size)

+    {

+        long usedHeapSize = alignUpToX(alignUpToX(pointerSize + refSize + 4, refSize) + size * (long)PrimitiveArrayImpl.ELEMENT_SIZE[(int) elementType], objectAlign);

+        return usedHeapSize;

+    }

+

+}

diff --git a/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofRandomAccessParser.java b/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofRandomAccessParser.java
index 8534350..b5af743 100644
--- a/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofRandomAccessParser.java
+++ b/plugins/org.eclipse.mat.hprof/src/org/eclipse/mat/hprof/HprofRandomAccessParser.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008, 2020 SAP AG and others.
+ * Copyright (c) 2008, 2021 SAP AG, Netflix, IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -14,6 +14,7 @@
  *******************************************************************************/
 package org.eclipse.mat.hprof;
 
+import java.io.EOFException;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -22,9 +23,12 @@
 import java.util.List;
 
 import org.eclipse.mat.SnapshotException;
+import org.eclipse.mat.hprof.AbstractParser.Constants.Record;
 import org.eclipse.mat.hprof.describer.Version;
 import org.eclipse.mat.hprof.ui.HprofPreferences;
+import org.eclipse.mat.parser.index.IIndexReader.IOne2LongIndex;
 import org.eclipse.mat.parser.io.BufferedRandomAccessInputStream;
+import org.eclipse.mat.parser.model.AbstractObjectImpl;
 import org.eclipse.mat.parser.model.ClassImpl;
 import org.eclipse.mat.parser.model.ClassLoaderImpl;
 import org.eclipse.mat.parser.model.InstanceImpl;
@@ -37,6 +41,7 @@
 import org.eclipse.mat.snapshot.model.IClass;
 import org.eclipse.mat.snapshot.model.IObject;
 import org.eclipse.mat.snapshot.model.IPrimitiveArray;
+import org.eclipse.mat.snapshot.model.ObjectReference;
 import org.eclipse.mat.util.MessageUtil;
 
 public class HprofRandomAccessParser extends AbstractParser
@@ -80,10 +85,14 @@
         in.close();
     }
 
-    public synchronized IObject read(int objectId, long position, ISnapshot dump) throws IOException, SnapshotException
+    public synchronized IObject read(int objectId, long position, ISnapshot dump, IOne2LongIndex o2hprof) throws IOException, SnapshotException
     {
         in.seek(position);
         int segmentType = in.readUnsignedByte();
+        if (objectId == -1)
+        {
+            segmentType = skipRecords(segmentType);
+        }
         switch (segmentType)
         {
             case Constants.DumpSegment.INSTANCE_DUMP:
@@ -114,14 +123,192 @@
         return answer;
     }
 
+    /**
+     * A reference to an object via its address
+     * which can handle the address not being in the index.
+     */
+    static class ObjectAddressReference extends ObjectReference
+    {
+        private static final long serialVersionUID = 1L;
+        transient IOne2LongIndex o2hprof;
+        transient ISnapshot snapshot;
+        transient HprofRandomAccessParser parser;
+        /**
+         * Construct an {@link ObjectAddressReference} which can be used to retrieve an
+         * object by address even if it has not been indexed.
+         * Used as a proxy by {@link ObjectReference} which can change the {@link ObjectReference#address}
+         * private field.
+         * @param snapshot the snapshot
+         * @param parser the HPROF parser used to build the IObject if required
+         * @param o2hprof the index from object ID to HPROF file position
+         * @param address the address of the object
+         */
+        public ObjectAddressReference(ISnapshot snapshot, HprofRandomAccessParser parser, IOne2LongIndex o2hprof, long address)
+        {
+            super(snapshot, address);
+            this.snapshot = snapshot;
+            this.o2hprof = o2hprof;
+            this.parser = parser;
+        }
+        @Override
+        public int getObjectId() throws SnapshotException
+        {
+            try
+            {
+                int id = super.getObjectId();
+                return id;
+            }
+            catch (SnapshotException e)
+            {
+                return -1;
+            }
+        }
+        /**
+         * Actually construct an IObject.
+         * First try constructing by object ID.
+         * If that fails then construct by address.
+         * Find the indexed objects before and after this.
+         * Parse the HPROF file between these two points looking for an
+         * object of the correct address.
+         */
+        @Override
+        public IObject getObject() throws SnapshotException
+        {
+            SnapshotException e1 = null;
+            try
+            {
+                int id = super.getObjectId();
+                if (id >= 0)
+                {
+                    IObject o = super.getObject();
+                    return o;
+                }
+            }
+            catch (SnapshotException e)
+            {
+                e1 = e;
+            }
+            // Find the object IDs before and after this object
+            int low = 0;
+            int high = o2hprof.size() - 1;
+            for (;low + 1 < high;)
+            {
+                int mid = (low + high) >>> 1;
+                long midAddr = snapshot.mapIdToAddress(mid);
+                if (getObjectAddress() < midAddr)
+                {
+                    high = mid;
+                }
+                else if (getObjectAddress() > midAddr)
+                {
+                    low = mid;
+                }
+                else
+                {
+                    low = mid;
+                    high = mid;
+                }
+            }
+            // Some objects don't have a position (classes?), so find valid file positions
+            long pos = 0;
+            while ((pos = o2hprof.get(low)) == 0 && low > 0)
+                --low;
+            if (pos == 0)
+            {
+                // There might be discarded objects before the first
+                Long lpos = (Long)snapshot.getSnapshotInfo().getProperty(HprofHeapObjectReader.HPROF_HEAP_START);
+                if (lpos != null)
+                    pos = lpos;
+                else while ((pos = o2hprof.get(low)) == 0 && low < o2hprof.size() - 1)
+                    ++low;
+            }
+            long posHigh = pos;
+            while (high >= 0 && (posHigh = o2hprof.get(high)) == 0 && high < o2hprof.size() - 1)
+                ++high;
+            if (posHigh == 0)
+            {
+                // There might be discarded objects after the last
+                Long olen = (Long)snapshot.getSnapshotInfo().getProperty(HprofHeapObjectReader.HPROF_LENGTH_PROPERTY);
+                posHigh = (olen != null) ? olen : Long.MAX_VALUE;
+            }
+            // Reparse the HPROF file looking for an object of the right address
+            try
+            {
+                do
+                {
+                    IObject o;
+                    synchronized (parser)
+                    {
+                        // Need final position without interference
+                        o = parser.read(-1, pos, snapshot, o2hprof);
+                        pos = parser.in.position();
+                    }
+                    if (o.getObjectAddress() == getObjectAddress())
+                    {
+                        // Class was discarded from the snapshot
+                        if (o.getClazz() == null)
+                            throw new IOException();
+                        ((AbstractObjectImpl)o).setSnapshot(snapshot);
+                        return o;
+                    }
+                    // Gone past the object address we want
+                    if (o.getObjectAddress() > getObjectAddress())
+                        break;
+                }
+                while (pos < posHigh);
+            }
+            catch (IOException e2)
+            {
+                if (e2 instanceof EOFException)
+                    throw e1;
+                throw new SnapshotException(e2);
+            }
+            // Not found, so throw the original exception
+            throw e1;
+        }
+    }
+
     private IObject readInstanceDump(int objectId, ISnapshot dump) throws IOException, SnapshotException
     {
         long address = in.readID(idSize);
-        if (in.skipBytes(8 + idSize) != 8 + idSize)
-            throw new IOException();
+        IClass oclazz;
+        if (objectId >= 0)
+        {
+            // Skip serial number, class ID, length
+            if (in.skipBytes(8 + idSize) != 8 + idSize)
+                throw new IOException();
+            oclazz = dump.getClassOf(objectId);
+        }
+        else
+        {
+            // skip serial number
+            if (in.skipBytes(4) != 4)
+                throw new IOException();
+            // class ID
+            long classAddr = in.readID(idSize);
+            // length
+            long len = in.readUnsignedInt();
+            try
+            {
+                int classID = dump.mapAddressToId(classAddr);
+                IObject io = dump.getObject(classID);
+                if (io instanceof IClass)
+                    oclazz = (IClass)io;
+                else
+                    throw new IOException();
+            }
+            catch (SnapshotException e)
+            {
+                // move to end of object
+                if (in.skipBytes(len) != len)
+                    throw new IOException();
+                // Invalid object, but might be good enough for skipping over
+                return new InstanceImpl(objectId, address, null, null);
+            }
+        }
 
         // check if we need to defer reading the class
-        List<IClass> hierarchy = resolveClassHierarchy(dump, dump.getClassOf(objectId));
+        List<IClass> hierarchy = resolveClassHierarchy(dump, oclazz);
         if (hierarchy == null)
         {
             throw new IOException(Messages.HprofRandomAccessParser_Error_DumpIncomplete);
@@ -156,15 +343,36 @@
 
         in.skipBytes(4);
         int size = in.readInt();
+        long len = (long)size * idSize;
 
         long arrayClassObjectID = in.readID(idSize);
 
-        IClass arrayType = (IClass) dump.getObject(dump.mapAddressToId(arrayClassObjectID));
+        int typeId;
+        if (objectId == -1)
+        {
+            try
+            {
+                typeId = dump.mapAddressToId(arrayClassObjectID);
+            }
+            catch (SnapshotException e)
+            {
+                ObjectArrayImpl array = new ObjectArrayImpl(objectId, id, null, size);
+                // Move to end of object
+                if (in.skipBytes(len) != len)
+                    throw new IOException();
+                return array;
+            }
+        }
+        else
+        {
+            typeId = dump.mapAddressToId(arrayClassObjectID);
+        }
+        IClass arrayType = (IClass) dump.getObject(typeId);
         if (arrayType == null)
             throw new RuntimeException(Messages.HprofRandomAccessParser_Error_MissingFakeClass);
 
         Object content = null;
-        if ((long)size * idSize < LAZY_LOADING_LIMIT)
+        if (len < LAZY_LOADING_LIMIT)
         {
             long[] data = new long[size];
             for (int ii = 0; ii < data.length; ii++)
@@ -174,6 +382,12 @@
         else
         {
             content = new ArrayDescription.Offline(false, in.position(), 0, size);
+            if (objectId == -1)
+            {
+                // Move to end of object
+                if (in.skipBytes(len) != len)
+                    throw new IOException();
+            }
         }
 
         ObjectArrayImpl array = new ObjectArrayImpl(objectId, id, (ClassImpl) arrayType, size);
@@ -205,6 +419,12 @@
         else
         {
             content = new ArrayDescription.Offline(true, in.position(), elementSize, arraySize);
+            if (objectId == -1)
+            {
+                // Move to end of object
+                if (in.skipBytes(len) != len)
+                    throw new IOException();
+            }
         }
 
         // lookup class by name
@@ -212,7 +432,15 @@
         String name = IPrimitiveArray.TYPE[(int) elementType];
         Collection<IClass> classes = dump.getClassesByName(name, false);
         if (classes == null || classes.isEmpty())
+        {
+            if (objectId == -1)
+            {
+                // Return a dummy array which can be ignored
+                PrimitiveArrayImpl array = new PrimitiveArrayImpl(objectId, id, (ClassImpl) clazz, arraySize, (int) elementType);
+                return array;
+            }
             throw new IOException(MessageUtil.format(Messages.HprofRandomAccessParser_Error_MissingClass, name));
+        }
         else if (classes.size() > 1)
             throw new IOException(MessageUtil.format(Messages.HprofRandomAccessParser_Error_DuplicateClass, name));
         else
@@ -247,4 +475,74 @@
         in.readFully(data);
         return data;
     }
+
+    private int skipRecords(int segmentType) throws IOException
+    {
+        boolean again = true;
+        do
+        {
+            int skip = -1;
+            switch (segmentType)
+            {
+                case Record.HEAP_DUMP_SEGMENT:
+                    skip = 8;
+                    break;
+                case Constants.DumpSegment.ROOT_UNKNOWN:
+                case Constants.DumpSegment.ROOT_STICKY_CLASS:
+                case Constants.DumpSegment.ROOT_MONITOR_USED:
+                    skip = idSize;
+                    break;
+                case Constants.DumpSegment.ROOT_JNI_GLOBAL:
+                    skip = idSize * 2;
+                    break;
+                case Constants.DumpSegment.ROOT_JNI_LOCAL:
+                case Constants.DumpSegment.ROOT_JAVA_FRAME:
+                case Constants.DumpSegment.ROOT_THREAD_OBJECT:
+                    skip = idSize + 8;
+                    break;
+                case Constants.DumpSegment.ROOT_NATIVE_STACK:
+                case Constants.DumpSegment.ROOT_THREAD_BLOCK:
+                    skip = idSize + 4;
+                    break;
+                case Constants.DumpSegment.CLASS_DUMP:
+                    skipClassDump();
+                    // Already skipped enough, so just reread
+                    skip = 0;
+                    break;
+                default:
+                    again = false;
+                    break;
+            }
+            if (skip >= 0)
+            {
+                // Skip over new segment header etc.
+                in.skipBytes(skip);
+                segmentType = in.readUnsignedByte();
+            }
+        }
+        while (again);
+        return segmentType;
+    }
+
+    private void skipClassDump() throws IOException
+    {
+        in.skipBytes(7 * idSize + 8);
+
+        int constantPoolSize = in.readUnsignedShort();
+        for (int ii = 0; ii < constantPoolSize; ii++)
+        {
+            in.skipBytes(2);
+            skipValue(in);
+        }
+
+        int numStaticFields = in.readUnsignedShort();
+        for (int i = 0; i < numStaticFields; i++)
+        {
+            in.skipBytes(idSize);
+            skipValue(in);
+        }
+
+        int numInstanceFields = in.readUnsignedShort();
+        in.skipBytes((idSize + 1) * numInstanceFields);
+    }
 }
diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/IObjectReader.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/IObjectReader.java
index 48050aa..b5a0f33 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/IObjectReader.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/IObjectReader.java
@@ -1,87 +1,88 @@
-/*******************************************************************************
- * Copyright (c) 2008, 2010 SAP AG and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- *    SAP AG - initial API and implementation
- *******************************************************************************/
-package org.eclipse.mat.parser;
-
-import java.io.IOException;
-
-import org.eclipse.mat.SnapshotException;
-import org.eclipse.mat.parser.model.ObjectArrayImpl;
-import org.eclipse.mat.parser.model.PrimitiveArrayImpl;
-import org.eclipse.mat.snapshot.ISnapshot;
-import org.eclipse.mat.snapshot.model.IObject;
-
-/**
- * Part of the parser which retrieves detailed information about an object
- */
-public interface IObjectReader
-{
-    /**
-     * Open the dump file associated with the snapshot
-     * @param snapshot
-     * @throws SnapshotException
-     * @throws IOException
-     */
-    void open(ISnapshot snapshot) //
-                    throws SnapshotException, IOException;
-
-    /**
-     * Get detailed information about an object
-     * @param objectId the object id
-     * @param snapshot the snapshot
-     * @return an IObject such as {@link org.eclipse.mat.parser.model.InstanceImpl}, {@link org.eclipse.mat.parser.model.ObjectArrayImpl}, {@link org.eclipse.mat.parser.model.PrimitiveArrayImpl}, {@link org.eclipse.mat.parser.model.ClassLoaderImpl}
-     * @throws SnapshotException
-     * @throws IOException
-     */
-    IObject read(int objectId, ISnapshot snapshot) //
-                    throws SnapshotException, IOException;
-
-    /**
-     * Get detailed information about a primitive array
-     * @param array the array
-     * @param offset where in the array to start
-     * @param length how much to read
-     * @return a byte[], short[], int[], long[], boolean[], char[], float[], double[]
-     * @throws IOException
-     * @throws SnapshotException
-     */
-    Object readPrimitiveArrayContent(PrimitiveArrayImpl array, int offset, int length) //
-                    throws IOException, SnapshotException;
-
-    /**
-     * Get detailed information about a object array
-     * @param array
-     * @param offset where in the array to start
-     * @param length how much to read
-     * @return an array of object addresses, with 0 for nulls 
-     * @throws IOException
-     * @throws SnapshotException
-     */
-    long[] readObjectArrayContent(ObjectArrayImpl array, int offset, int length) //
-                    throws IOException, SnapshotException;
-
-    /**
-     * Get additional information about the snapshot
-     * @param addon type of the additional information
-     * @return the additional information
-     * @throws SnapshotException
-     */
-    <A> A getAddon(Class<A> addon) //
-                    throws SnapshotException;
-
-    /**
-     * tidy up when snapshot no longer required
-     * @throws IOException
-     */
-    void close() throws IOException;
-
-}
+/*******************************************************************************

+ * Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License 2.0

+ * which accompanies this distribution, and is available at

+ * https://www.eclipse.org/legal/epl-2.0/

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ * Contributors:

+ *    SAP AG - initial API and implementation

+ *    Andrew Johnson (IBM Corporation) - Javadoc

+ *******************************************************************************/

+package org.eclipse.mat.parser;

+

+import java.io.IOException;

+

+import org.eclipse.mat.SnapshotException;

+import org.eclipse.mat.parser.model.ObjectArrayImpl;

+import org.eclipse.mat.parser.model.PrimitiveArrayImpl;

+import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.model.IObject;

+

+/**

+ * Part of the parser which retrieves detailed information about an object

+ */

+public interface IObjectReader

+{

+    /**

+     * Open the dump file associated with the snapshot

+     * @param snapshot the snapshot

+     * @throws SnapshotException some other problem

+     * @throws IOException an IO problem, or corrupt indexes or unexpected data in the dump

+     */

+    void open(ISnapshot snapshot) //

+                    throws SnapshotException, IOException;

+

+    /**

+     * Get detailed information about an object

+     * @param objectId the object id

+     * @param snapshot the snapshot

+     * @return an IObject such as {@link org.eclipse.mat.parser.model.InstanceImpl}, {@link org.eclipse.mat.parser.model.ObjectArrayImpl}, {@link org.eclipse.mat.parser.model.PrimitiveArrayImpl}, {@link org.eclipse.mat.parser.model.ClassLoaderImpl}

+     * @throws SnapshotException some other problem such as where the object is incompatible with the snapshot

+     * @throws IOException an IO problem or unexpected data in the dump

+     */

+    IObject read(int objectId, ISnapshot snapshot) //

+                    throws SnapshotException, IOException;

+

+    /**

+     * Get detailed information about a primitive array

+     * @param array the array

+     * @param offset where in the array to start

+     * @param length how much to read

+     * @return a byte[], short[], int[], long[], boolean[], char[], float[], double[]

+     * @throws SnapshotException some other problem such as where the object is incompatible with the snapshot

+     * @throws IOException an IO problem or unexpected data in the dump

+     */

+    Object readPrimitiveArrayContent(PrimitiveArrayImpl array, int offset, int length) //

+                    throws IOException, SnapshotException;

+

+    /**

+     * Get detailed information about a object array

+     * @param array

+     * @param offset where in the array to start

+     * @param length how much to read

+     * @return an array of object addresses, with 0 for nulls 

+     * @throws SnapshotException some other problem such as where the object is incompatible with the snapshot

+     * @throws IOException an IO problem or unexpected data in the dump

+     */

+    long[] readObjectArrayContent(ObjectArrayImpl array, int offset, int length) //

+                    throws IOException, SnapshotException;

+

+    /**

+     * Get additional information about the snapshot

+     * @param addon type of the additional information

+     * @return the additional information

+     * @throws SnapshotException

+     */

+    <A> A getAddon(Class<A> addon) //

+                    throws SnapshotException;

+

+    /**

+     * tidy up when snapshot no longer required

+     * @throws IOException should not normally occur

+     */

+    void close() throws IOException;

+

+}

diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/AbstractObjectImpl.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/AbstractObjectImpl.java
index 0bf716a..535559b 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/AbstractObjectImpl.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/AbstractObjectImpl.java
@@ -1,10 +1,10 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2020 SAP AG, IBM Corporation and others.

+ * Copyright (c) 2008, 2021 SAP AG, IBM Corporation and others.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

- * https://www.eclipse.org/legal/epl-2.0/
- *
+ * https://www.eclipse.org/legal/epl-2.0/

+ *

  * SPDX-License-Identifier: EPL-2.0

  *

  * Contributors:

@@ -140,7 +140,20 @@
     {

         try

         {

-            return source.getRetainedHeapSize(getObjectId());

+            int objId;

+            try

+            {

+                objId = getObjectId();

+            }

+            catch (RuntimeException e)

+            {

+                Throwable cause = e.getCause();

+                if (cause instanceof SnapshotException)

+                    return 0;

+                else

+                    throw e;

+            }

+            return source.getRetainedHeapSize(objId);

         }

         catch (SnapshotException e)

         {

@@ -290,7 +303,20 @@
 

     public GCRootInfo[] getGCRootInfo() throws SnapshotException

     {

-        return source.getGCRootInfo(getObjectId());

+        int objId;

+        try

+        {

+            objId = getObjectId();

+        }

+        catch (RuntimeException e)

+        {

+            Throwable cause = e.getCause();

+            if (cause instanceof SnapshotException)

+                throw (SnapshotException)cause;

+            else

+                throw e;

+        }

+        return source.getGCRootInfo(objId);

     }

 

     @Override

diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/InstanceImpl.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/InstanceImpl.java
index 40aee4e..1d594b7 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/InstanceImpl.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/InstanceImpl.java
@@ -151,9 +151,25 @@
     @Override

     public long getUsedHeapSize()

     {

-        try {

-            return getSnapshot().getHeapSize(getObjectId());

-        } catch (SnapshotException e) {

+        try

+        {

+            int objId;

+            try

+            {

+                objId = getObjectId();

+            }

+            catch (RuntimeException e)

+            {

+                Throwable cause = e.getCause();

+                if (cause instanceof SnapshotException)

+                    throw (SnapshotException) cause;

+                else

+                    throw e;

+            }

+            return getSnapshot().getHeapSize(objId);

+        }

+        catch (SnapshotException e)

+        {

             return classInstance.getHeapSizePerInstance();

         }

     }

diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/ObjectArrayImpl.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/ObjectArrayImpl.java
index e0d27eb..55840b8 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/ObjectArrayImpl.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/ObjectArrayImpl.java
@@ -51,9 +51,25 @@
     @Override

     public long getUsedHeapSize()

     {

-        try {

-            return getSnapshot().getHeapSize(getObjectId());

-        } catch (SnapshotException e) {

+        try

+        {

+            int objId;

+            try

+            {

+                objId = getObjectId();

+            }

+            catch (RuntimeException e)

+            {

+                Throwable cause = e.getCause();

+                if (cause instanceof SnapshotException)

+                    throw (SnapshotException) cause;

+                else

+                    throw e;

+            }

+            return getSnapshot().getHeapSize(objId);

+        }

+        catch (SnapshotException e)

+        {

             return doGetUsedHeapSize(classInstance, getLength());

         }

     }

diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/PrimitiveArrayImpl.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/PrimitiveArrayImpl.java
index 9cd19e6..f24c6e6 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/PrimitiveArrayImpl.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/model/PrimitiveArrayImpl.java
@@ -128,9 +128,25 @@
     @Override

     public long getUsedHeapSize()

     {

-        try {

-            return getSnapshot().getHeapSize(getObjectId());

-        } catch (SnapshotException e) {

+        try

+        {

+            int objId;

+            try

+            {

+                objId = getObjectId();

+            }

+            catch (RuntimeException e)

+            {

+                Throwable cause = e.getCause();

+                if (cause instanceof SnapshotException)

+                    throw (SnapshotException) cause;

+                else

+                    throw e;

+            }

+            return getSnapshot().getHeapSize(objId);

+        }

+        catch (SnapshotException e)

+        {

             return doGetUsedHeapSize(classInstance, getLength(), type);

         }

     }

diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/CreateCollectionDump.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/CreateCollectionDump.java
index 145dde9..ac18398 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/CreateCollectionDump.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/CreateCollectionDump.java
@@ -115,7 +115,7 @@
                         }

                     }

                     if (!found)

-                        System.out.println("Missing entry set class " + s2.getClass());

+                        System.out.println("Missing entry set " + s2.getClass());

                 }

             }

         }

diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/TestUnreachableObjects.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/TestUnreachableObjects.java
index 95679bb..e7e66b3 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/TestUnreachableObjects.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/TestUnreachableObjects.java
@@ -1,10 +1,10 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2020 SAP AG and IBM Corporation.

+ * Copyright (c) 2008, 2021 SAP AG and IBM Corporation.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

- * https://www.eclipse.org/legal/epl-2.0/
- *
+ * https://www.eclipse.org/legal/epl-2.0/

+ *

  * SPDX-License-Identifier: EPL-2.0

  *

  * Contributors:

@@ -13,17 +13,31 @@
  *******************************************************************************/

 package org.eclipse.mat.tests.snapshot;

 

+import static org.hamcrest.MatcherAssert.assertThat;

+import static org.hamcrest.Matchers.greaterThanOrEqualTo;

+import static org.hamcrest.Matchers.lessThanOrEqualTo;

+import static org.junit.Assert.assertTrue;

+

 import java.util.HashMap;

 import java.util.Map;

 

 import org.eclipse.mat.SnapshotException;

+import org.eclipse.mat.query.IResult;

 import org.eclipse.mat.snapshot.ClassHistogramRecord;

 import org.eclipse.mat.snapshot.Histogram;

 import org.eclipse.mat.snapshot.ISnapshot;

 import org.eclipse.mat.snapshot.UnreachableObjectsHistogram;

+import org.eclipse.mat.snapshot.model.IClass;

+import org.eclipse.mat.snapshot.model.IObject;

+import org.eclipse.mat.snapshot.model.IObjectArray;

+import org.eclipse.mat.snapshot.model.NamedReference;

+import org.eclipse.mat.snapshot.query.SnapshotQuery;

 import org.eclipse.mat.tests.TestSnapshots;

+import org.eclipse.mat.tests.snapshot.GeneralSnapshotTests.CheckedProgressListener;

 import org.eclipse.mat.util.VoidProgressListener;

+import org.junit.Rule;

 import org.junit.Test;

+import org.junit.rules.ErrorCollector;

 

 public class TestUnreachableObjects

 {

@@ -64,6 +78,43 @@
         compareDiscard(TestSnapshots.IBM_JDK6_32BIT_SYSTEM);

     }

 

+    @Test

+    public void testDiscardReportSunJDK5_64() throws SnapshotException

+    {

+        discardComponentReport(TestSnapshots.SUN_JDK5_64BIT);

+    }

+

+    @Test

+    public void testDiscardReportSunJDK6_32() throws SnapshotException

+    {

+        discardComponentReport(TestSnapshots.SUN_JDK6_32BIT);

+    }

+

+    @Test

+    public void testDiscardReportIBMJDK6_32_SYSTEM() throws SnapshotException

+    {

+        discardComponentReport(TestSnapshots.IBM_JDK6_32BIT_SYSTEM);

+    }

+

+

+    @Test

+    public void testDiscardStringsSunJDK5_64() throws SnapshotException

+    {

+        discardStringsValue(TestSnapshots.SUN_JDK5_64BIT);

+    }

+

+    @Test

+    public void testDiscardStringsSunJDK6_32() throws SnapshotException

+    {

+        discardStringsValue(TestSnapshots.SUN_JDK6_32BIT);

+    }

+

+    @Test

+    public void testDiscardStringsIBMJDK6_32_SYSTEM() throws SnapshotException

+    {

+        discardStringsValue(TestSnapshots.IBM_JDK6_32BIT_SYSTEM);

+    }

+

     // IBM PHD files do not have accurate roots, so this test won't work for IBM_JDK6_32BIT_HEAP

 

     private void compareUnreachable(String snapshotName) throws SnapshotException

@@ -85,7 +136,7 @@
         options.put("keep_unreachable_objects", "true");

 

         ISnapshot unreachables = TestSnapshots.getSnapshot(snapshotName, options, true);

-        

+

         Map<String, String> options2 = new HashMap<String, String>();

         options2.put("keep_unreachable_objects", "true");

         options2.put("discard_ratio", "60");

@@ -99,6 +150,81 @@
         classic.dispose();

     }

 

+    @Rule

+    public ErrorCollector collector = new ErrorCollector();

+    private void discardComponentReport(String snapshotName) throws SnapshotException

+    {

+        Map<String, String> options2 = new HashMap<String, String>();

+        options2.put("keep_unreachable_objects", "false");

+        options2.put("discard_ratio", "60");

+        options2.put("discard_pattern", "char\\[\\]|java\\.lang\\.String");

+        options2.put("discard_offset", "80");

+        options2.put("discard_seed", "2");

+        ISnapshot snapshot = TestSnapshots.getSnapshot(snapshotName, options2, true);

+        SnapshotQuery query = SnapshotQuery.lookup("component_report_top", snapshot);

+        query.setArgument("aggressive", true);

+        IResult result = query.execute(new CheckedProgressListener(collector));

+        assertTrue(result != null);

+        // Tidy up these pristine snapshots early

+        snapshot.dispose();

+    }

+

+    private void discardStringsValue(String snapshotName) throws SnapshotException

+    {

+        Map<String, String> options2 = new HashMap<String, String>();

+        options2.put("keep_unreachable_objects", "false");

+        options2.put("discard_ratio", "60");

+        options2.put("discard_pattern", "char\\[\\]|java\\.lang\\.String");

+        options2.put("discard_offset", "80");

+        options2.put("discard_seed", "2");

+        ISnapshot snapshot = TestSnapshots.getSnapshot(snapshotName, options2, true);

+        int len = 0;

+        int bad = 0;

+        int unindexed = 0;

+        for (IClass cls : snapshot.getClassesByName("java.lang.String[]", false))

+        {

+            for (int id : cls.getObjectIds())

+            {

+                IObjectArray ia = (IObjectArray) snapshot.getObject(id);

+                for (NamedReference ref : ia.getOutboundReferences())

+                {

+                    try

+                    {

+                        IObject s = ref.getObject();

+                        String v = s.getClassSpecificName();

+                        if (v != null)

+                        {

+                            len += v.length();

+                            if (v.length() > 0)

+                            {

+                                try

+                                {

+                                    int id1 = s.getObjectId();

+                                    if (id1 < 0)

+                                        ++unindexed;

+                                }

+                                catch (RuntimeException e)

+                                {

+                                    if (e.getCause() instanceof SnapshotException)

+                                        ++unindexed;

+                                }

+                            }

+                        }

+                    }

+                    catch (SnapshotException e)

+                    {

+                        ++bad;

+                    }

+                }

+            }

+        }

+        assertThat("Strings should be readable", len, greaterThanOrEqualTo(800));

+        assertThat("Not too many bad reads", bad, lessThanOrEqualTo(300));

+        assertThat("Unindexed objects have resolved", unindexed, greaterThanOrEqualTo(15));

+        // Tidy up these pristine snapshots early

+        snapshot.dispose();

+    }

+

     private void compare(ISnapshot unreachables, ISnapshot classic) throws SnapshotException

     {

         Histogram fullHistogram = unreachables.getHistogram(new VoidProgressListener());

diff --git a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/actions/CopyActions.java b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/actions/CopyActions.java
index 87b297c..878ee3b 100644
--- a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/actions/CopyActions.java
+++ b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/actions/CopyActions.java
@@ -17,11 +17,13 @@
 

 import org.eclipse.mat.SnapshotException;

 import org.eclipse.mat.query.IContextObject;

+import org.eclipse.mat.query.IContextObjectSet;

 import org.eclipse.mat.query.IQuery;

 import org.eclipse.mat.query.IResult;

 import org.eclipse.mat.query.annotations.Argument;

 import org.eclipse.mat.query.annotations.Icon;

 import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.OQL;

 import org.eclipse.mat.snapshot.model.IClass;

 import org.eclipse.mat.snapshot.model.IObject;

 import org.eclipse.mat.ui.Messages;

@@ -61,6 +63,10 @@
 

                     appendValue(buf, object);

                 }

+                else

+                {

+                    appendValue(buf, argument);

+                }

                 listener.worked(1);

                 if (listener.isCanceled())

                     break;

@@ -88,6 +94,9 @@
     }

 

     protected abstract void appendValue(StringBuilder buf, IObject object) throws SnapshotException;

+    protected void appendValue(StringBuilder buf, IContextObject ctx) throws SnapshotException

+    {}

+

 

     // //////////////////////////////////////////////////////////////

     // several copy actions

@@ -100,6 +109,29 @@
         {

             buf.append("0x").append(Long.toHexString(object.getObjectAddress()));//$NON-NLS-1$

         }

+        protected void appendValue(StringBuilder buf, IContextObject ctx) throws SnapshotException

+        {

+            if (ctx instanceof IContextObjectSet)

+            {

+                IContextObjectSet ctxs = (IContextObjectSet)ctx;

+                String oql = ctxs.getOQL();

+                if (oql != null && ctxs.getObjectIds().length == 0)

+                {

+                    String dummy = OQL.forAddress(0x0);

+                    if (oql.startsWith(dummy.substring(0, dummy.length() - 3)))

+                    {

+                        // Special OQL indicating unindexed object

+                        String addr = oql.substring(dummy.length() - 3);

+                        if (addr.matches("0[xX][0-9A-Fa-f]+")) //$NON-NLS-1$

+                        {

+                            if (buf.length() > 0)

+                                buf.append(System.lineSeparator());

+                            buf.append(addr);

+                        }

+                    }

+                }

+            }

+        }

     }

 

     @Icon("/icons/copy.gif")

diff --git a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/FieldsLabelProvider.java b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/FieldsLabelProvider.java
index f83144a..a17e458 100644
--- a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/FieldsLabelProvider.java
+++ b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/FieldsLabelProvider.java
@@ -1,10 +1,10 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2010 SAP AG.

+ * Copyright (c) 2008, 2021 SAP AG.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

- * https://www.eclipse.org/legal/epl-2.0/
- *
+ * https://www.eclipse.org/legal/epl-2.0/

+ *

  * SPDX-License-Identifier: EPL-2.0

  *

  * Contributors:

@@ -20,6 +20,7 @@
 import org.eclipse.mat.snapshot.ISnapshot;

 import org.eclipse.mat.snapshot.model.Field;

 import org.eclipse.mat.snapshot.model.IObject;

+import org.eclipse.mat.snapshot.model.ObjectReference;

 import org.eclipse.mat.ui.MemoryAnalyserPlugin;

 import org.eclipse.mat.ui.snapshot.views.inspector.FieldsContentProvider.MoreNode;

 import org.eclipse.swt.SWT;

@@ -99,7 +100,9 @@
             ISnapshot snapshot = this.inspectorView.snapshot;

             if (snapshot != null)

             {

-                IObject object = snapshot.getObject(snapshot.mapAddressToId(objectAddress));

+                // Could work for unindexed objects

+                ObjectReference ref = new ObjectReference(snapshot, objectAddress);

+                IObject object = ref.getObject();

                 String text = object.getClassSpecificName();

                 if (text == null)

                     text = object.getTechnicalName();

diff --git a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorContextProvider.java b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorContextProvider.java
index 225a490..be2064b 100644
--- a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorContextProvider.java
+++ b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorContextProvider.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2010 SAP AG.

+ * Copyright (c) 2008, 2021 SAP AG and IBM Corporation.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

@@ -9,13 +9,16 @@
  *

  * Contributors:

  *    SAP AG - initial API and implementation

+ *    Andrew Johnson (IBM) - handle missing objects

  *******************************************************************************/

 package org.eclipse.mat.ui.snapshot.views.inspector;

 

 import org.eclipse.mat.SnapshotException;

 import org.eclipse.mat.query.ContextProvider;

 import org.eclipse.mat.query.IContextObject;

+import org.eclipse.mat.query.IContextObjectSet;

 import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.OQL;

 import org.eclipse.mat.snapshot.model.IClass;

 import org.eclipse.mat.ui.snapshot.views.inspector.InspectorView.BaseNode;

 

@@ -58,6 +61,28 @@
                             return node.objectId;

                         }

                     };

+                if (node.addr != Long.MIN_VALUE)

+                    return new IContextObjectSet()

+                    {

+

+                        @Override

+                        public int getObjectId()

+                        {

+                            return -1;

+                        }

+

+                        @Override

+                        public int[] getObjectIds()

+                        {

+                            return new int[0];

+                        }

+

+                        @Override

+                        public String getOQL()

+                        {

+                            return OQL.forAddress(node.addr);

+                        }

+                    };

             }

             else if (row instanceof IClass)

             {

@@ -75,6 +100,32 @@
         }

         catch (SnapshotException e)

         {

+            if (row instanceof NamedReferenceNode)

+            {

+                NamedReferenceNode node = (NamedReferenceNode) row;

+                return new IContextObjectSet() {

+

+                    @Override

+                    public int getObjectId()

+                    {

+                        return -1;

+                    }

+

+                    @Override

+                    public int[] getObjectIds()

+                    {

+                        return new int[0];

+                    }

+

+                    @Override

+                    public String getOQL()

+                    {

+                        // Used when the address does not convert to an object ID,

+                        // such as when objects are discarded.

+                        return OQL.forAddress(node.objectAddress);

+                    }

+                };

+            }

             throw new RuntimeException(e);

         }

     }

diff --git a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java
index a59d411..6cf83f5 100644
--- a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java
+++ b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/InspectorView.java
@@ -50,13 +50,16 @@
 import org.eclipse.jface.viewers.Viewer;

 import org.eclipse.mat.SnapshotException;

 import org.eclipse.mat.query.IContextObject;

+import org.eclipse.mat.query.IContextObjectSet;

 import org.eclipse.mat.snapshot.ISnapshot;

+import org.eclipse.mat.snapshot.OQL;

 import org.eclipse.mat.snapshot.model.GCRootInfo;

 import org.eclipse.mat.snapshot.model.IClass;

 import org.eclipse.mat.snapshot.model.IInstance;

 import org.eclipse.mat.snapshot.model.IObject;

 import org.eclipse.mat.snapshot.model.IObjectArray;

 import org.eclipse.mat.snapshot.model.IPrimitiveArray;

+import org.eclipse.mat.snapshot.model.ObjectReference;

 import org.eclipse.mat.ui.MemoryAnalyserPlugin;

 import org.eclipse.mat.ui.Messages;

 import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter;

@@ -123,21 +126,43 @@
     /* package */static class BaseNode

     {

         int objectId;

+        long addr;

 

         public BaseNode(int objectId)

         {

+            this(objectId, 0);

+        }

+        public BaseNode(int objectId, long addr)

+        {

             this.objectId = objectId;

+            this.addr = addr;

+        }

+

+        static int objectID(IObject object)

+        {

+            if (object == null)

+                return -1;

+            try

+            {

+                return object.getObjectId();

+            }

+            catch (RuntimeException e)

+            {

+                if (e.getCause() instanceof SnapshotException)

+                    return -1;

+                throw e;

+            }

         }

     }

 

-    private class ObjectNode extends BaseNode

+    private static class ObjectNode extends BaseNode

     {

         String label;

         int imageType;

 

         public ObjectNode(IObject object)

         {

-            super(object.getObjectId());

+            super(objectID(object), object.getObjectAddress());

             this.label = object.getTechnicalName();

             this.imageType = ImageHelper.getType(object);

         }

@@ -209,19 +234,19 @@
         }

     }

 

-    public class InfoItem extends BaseNode

+    public static class InfoItem extends BaseNode

     {

         private ImageDescriptor descriptor;

         private String text;

 

         public InfoItem(ImageDescriptor descriptor, String text)

         {

-            this(-1, descriptor, text);

+            this(null, descriptor, text);

         }

 

-        public InfoItem(int objectId, ImageDescriptor descriptor, String text)

+        public InfoItem(IObject object, ImageDescriptor descriptor, String text)

         {

-            super(objectId);

+            super(objectID(object), object != null ? object.getObjectAddress() : 0);

             this.descriptor = descriptor;

             this.text = text;

         }

@@ -814,7 +839,21 @@
                 objectSet = (IContextObject) object;

         }

 

-        if (objectSet == null || objectSet.getObjectId() < 0)

+        long addr = 0;

+        if (objectSet instanceof IContextObjectSet)

+        {

+            String oql = ((IContextObjectSet)objectSet).getOQL();

+            if (oql != null && ((IContextObjectSet)objectSet).getObjectIds().length == 0)

+            {

+                String addr0 = OQL.forAddress(0);

+                addr0 = addr0.substring(0, addr0.length() - 1);

+                if (oql.startsWith(addr0.substring(0, addr0.length())) && oql.substring(addr0.length() - 2).matches("0[xX][0-9a-fA-F]+")) //$NON-NLS-1$

+                {

+                    addr = Long.parseUnsignedLong(oql.substring(addr0.length()), 16);

+                }

+            }

+        }

+        if (objectSet == null || (objectSet.getObjectId() < 0 && addr == 0))

         {

             clearInput();

         }

@@ -828,8 +867,17 @@
             {

                 int current = ((Integer) data).intValue();

                 if (current == objectId)

-                    return;

+                {

+                    Object addr0 = topTableViewer.getData("address"); //$NON-NLS-1$

+                    if (addr0 != null)

+                    {

+                        long currentAddr = ((Long) addr0).longValue();

+                        if (currentAddr == addr)

+                            return;

+                    }

+                }

             }

+            final long objectAddress = addr;

 

             final ISnapshot savedSnapshot = snapshot;

 

@@ -844,7 +892,11 @@
                         if (snapshot == null || savedSnapshot != snapshot)

                             return Status.OK_STATUS;

 

-                        final IObject object = savedSnapshot.getObject(objectId);

+                        final IObject object;

+                        if (objectId >= 0)

+                            object = savedSnapshot.getObject(objectId);

+                        else

+                            object = (new ObjectReference(savedSnapshot, objectAddress)).getObject();

 

                         // prepare object info

                         final List<Object> classInfos = prepareClassInfo(object);

@@ -864,6 +916,7 @@
                             {

                                 topTableViewer.setInput(classInfos);

                                 topTableViewer.setData("input", objectId);//$NON-NLS-1$

+                                topTableViewer.setData("address", object.getObjectAddress());//$NON-NLS-1$

                                 staticsTable.setInput(staticFields);

                                 attributesTable.setInput(attributeFields);

                                 updateVisualViewer(toShow);

@@ -1021,7 +1074,7 @@
                 {

                     List<Object> details = new ArrayList<Object>();

 

-                    details.add(new InfoItem(object.getObjectId(), MemoryAnalyserPlugin

+                    details.add(new InfoItem(object, MemoryAnalyserPlugin

                                     .getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.ID), "0x"//$NON-NLS-1$

                                     + Long.toHexString(object.getObjectAddress())));

 

@@ -1031,7 +1084,7 @@
                     int p = className.lastIndexOf('.');

                     if (p < 0) // primitive

                     {

-                        InfoItem item = new InfoItem(object.getObjectId(), ImageHelper.getImageDescriptor(ImageHelper

+                        InfoItem item = new InfoItem(object, ImageHelper.getImageDescriptor(ImageHelper

                                         .getType(object)), className);

                         details.add(item);

                         details.add(new InfoItem(MemoryAnalyserPlugin

@@ -1039,7 +1092,7 @@
                     }

                     else

                     {

-                        details.add(new InfoItem(object.getObjectId(), ImageHelper.getImageDescriptor(ImageHelper

+                        details.add(new InfoItem(object, ImageHelper.getImageDescriptor(ImageHelper

                                         .getType(object)), className.substring(p + 1)));

                         details.add(new InfoItem(MemoryAnalyserPlugin

                                         .getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.PACKAGE), className

@@ -1053,7 +1106,7 @@
 

                     if (superClass != null)

                     {

-                        details.add(new InfoItem(superClass.getObjectId(), MemoryAnalyserPlugin

+                        details.add(new InfoItem(superClass, MemoryAnalyserPlugin

                                         .getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.SUPERCLASS), superClass

                                         .getName()));

                     }

@@ -1071,7 +1124,15 @@
                                     .getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.SIZE), MessageUtil.format(

                                     Messages.InspectorView_retainedSize, object.getRetainedHeapSize())));

 

-                    GCRootInfo[] gc = object.getGCRootInfo();

+                    GCRootInfo[] gc;

+                    try

+                    {

+                        gc = object.getGCRootInfo();

+                    }

+                    catch (SnapshotException e)

+                    {

+                        gc = null;

+                    }

                     details.add(gc != null ? (Object) gc : new InfoItem(MemoryAnalyserPlugin

                                     .getImageDescriptor(ImageHelper.Decorations.GC_ROOT),

                                     Messages.InspectorView_noGCRoot));

diff --git a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/LazyFields.java b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/LazyFields.java
index a429672..e3d26d6 100644
--- a/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/LazyFields.java
+++ b/plugins/org.eclipse.mat.ui/src/org/eclipse/mat/ui/snapshot/views/inspector/LazyFields.java
@@ -1,14 +1,15 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2010 SAP AG.

+ * Copyright (c) 2008, 2021 SAP AG and IBM Corporation.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License 2.0

  * which accompanies this distribution, and is available at

- * https://www.eclipse.org/legal/epl-2.0/
- *
+ * https://www.eclipse.org/legal/epl-2.0/

+ *

  * SPDX-License-Identifier: EPL-2.0

  *

  * Contributors:

  *    SAP AG - initial API and implementation

+ *    Andrew Johnson - read discarded objects by address

  *******************************************************************************/

 package org.eclipse.mat.ui.snapshot.views.inspector;

 

@@ -34,6 +35,7 @@
     private WeakReference<O> array;

 

     private int objectId;

+    private long objectAddr;

 

     protected List<Object> cache = new ArrayList<Object>();

 

@@ -44,7 +46,7 @@
             this.snapshot = new WeakReference<ISnapshot>(object.getSnapshot());

             this.array = new WeakReference<O>(object);

 

-            this.objectId = object.getObjectId();

+            this.objectAddr = object.getObjectAddress();

         }

     }

 

@@ -73,7 +75,8 @@
 

             try

             {

-                object = (O) snapshot.getObject(objectId);

+                ObjectReference ref = new ObjectReference(snapshot, objectAddr);

+                object = (O) ref.getObject();

                 this.array = new WeakReference<O>(object);

             }

             catch (SnapshotException e)