-
Notifications
You must be signed in to change notification settings - Fork 3
/
MultiplePathsFromGCRootsComputerImpl.java
365 lines (322 loc) · 11.4 KB
/
MultiplePathsFromGCRootsComputerImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/*******************************************************************************
* Copyright (c) 2008, 2023 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) - performance improvements
*******************************************************************************/
package org.eclipse.mat.parser.internal.snapshot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.BitField;
import org.eclipse.mat.collect.QueueInt;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.parser.index.IIndexReader;
import org.eclipse.mat.parser.internal.Messages;
import org.eclipse.mat.parser.internal.SnapshotImpl;
import org.eclipse.mat.snapshot.IMultiplePathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.MultiplePathsFromGCRootsClassRecord;
import org.eclipse.mat.snapshot.MultiplePathsFromGCRootsRecord;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.snapshot.model.ObjectReference;
import org.eclipse.mat.util.IProgressListener;
public class MultiplePathsFromGCRootsComputerImpl implements IMultiplePathsFromGCRootsComputer
{
int[] objectIds; // the initial objects
Object[] paths; // paths for each of the objects
SnapshotImpl snapshot; // snapshot
IIndexReader.IOne2ManyIndex outboundIndex; // outbound references index
private BitField excludeInstances;
private SetInt excludeClasses;
private Map<IClass, Set<String>> excludeMap;
private boolean pathsCalculated;
private static final int NOT_VISITED = -2;
private static final int NO_PARENT = -1;
public MultiplePathsFromGCRootsComputerImpl(int[] objectIds, Map<IClass, Set<String>> excludeMap, SnapshotImpl snapshot) throws SnapshotException
{
this.snapshot = snapshot;
this.objectIds = objectIds;
this.excludeMap = excludeMap;
outboundIndex = snapshot.getIndexManager().outbound;
if (excludeMap != null)
{
initExcludes();
}
}
private void initExcludes() throws SnapshotException
{
excludeInstances = new BitField(snapshot.getIndexManager().o2address().size());
excludeClasses = new SetInt();
for (IClass clazz : excludeMap.keySet())
{
int[] objects = clazz.getObjectIds();
for (int objId : objects)
{
excludeInstances.set(objId);
}
excludeClasses.add(clazz.getObjectId());
}
}
private void computePaths(IProgressListener progressListener) throws SnapshotException
{
ArrayList<int[]> pathsList = new ArrayList<int[]>();
// make a breadth first search for the objects, starting from the roots
int[] parent = bfs(progressListener);
// then get the shortest path per object
for (int i = 0; i < objectIds.length; i++)
{
int[] path = getPathFromBFS(objectIds[i], parent);
/*
* if there is an exclude filter, for some objects there could be no
* path, i.e. null is expected then
*/
if (path != null)
{
pathsList.add(path);
}
if (i % 1000 == 0)
{
if (progressListener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
}
pathsCalculated = true;
paths = pathsList.toArray();
}
public MultiplePathsFromGCRootsRecord[] getPathsByGCRoot(IProgressListener progressListener) throws SnapshotException
{
if (!pathsCalculated)
{
computePaths(progressListener);
}
MultiplePathsFromGCRootsRecord dummy = new MultiplePathsFromGCRootsRecord(-1, -1, snapshot);
for (int i = 0; i < paths.length; i++)
{
dummy.addPath((int[]) paths[i]);
}
return dummy.nextLevel();
}
public Object[] getAllPaths(IProgressListener progressListener) throws SnapshotException
{
if (!pathsCalculated)
{
computePaths(progressListener);
}
return paths;
}
public MultiplePathsFromGCRootsClassRecord[] getPathsGroupedByClass(boolean startFromTheGCRoots, IProgressListener progressListener)
throws SnapshotException
{
if (!pathsCalculated)
{
computePaths(progressListener);
}
MultiplePathsFromGCRootsClassRecord dummy = new MultiplePathsFromGCRootsClassRecord(null, -1, startFromTheGCRoots, snapshot);
for (int i = 0; i < paths.length; i++)
{
dummy.addPath((int[]) paths[i]);
}
return dummy.nextLevel();
}
private boolean refersOnlyThroughExcluded(int referrerId, int referentId, List<NamedReference> refCache)
throws SnapshotException
{
if (excludeInstances.get(referrerId))
{
IObject referrerObject = snapshot.getObject(referrerId);
return checkExcludeFields(referrerId, referentId, refCache, referrerObject, referrerObject.getClazz());
}
else if (excludeClasses.contains(referrerId))
{
IClass referrerObject = (IClass) snapshot.getObject(referrerId);
return checkExcludeFields(referrerId, referentId, refCache, referrerObject, referrerObject);
}
else
{
return false;
}
}
private boolean checkExcludeFields(int referrerId, int referentId, List<NamedReference> refCache,
IObject referrerObject, IClass referrerClass) throws SnapshotException
{
Set<String> excludeFields = excludeMap.get(referrerClass);
if (excludeFields == null)
return true; // treat null as all fields
long referentAddr = snapshot.mapIdToAddress(referentId);
// Only bother doing the sort if there are several entries
final int minsort = 10;
boolean sorted;
if (refCache.isEmpty())
{
List<NamedReference> refs = referrerObject.getOutboundReferences();
refCache.addAll(refs);
sorted = refCache.size() >= minsort;
if (sorted)
{
refCache.sort(CompObjectReference.INSTANCE);
}
}
else
{
sorted = refCache.size() >= minsort;
}
int idx;
if (sorted)
{
/*
* If there are duplicate addresses then this will find one,
* but to find all must scan forwards and backwards.
*/
idx = Collections.binarySearch(refCache, new ObjectReference(snapshot, referentAddr),
CompObjectReference.INSTANCE);
if (idx < 0)
return true;
// Find the first
while (idx > 0 && refCache.get(idx - 1).getObjectAddress() == referentAddr)
--idx;
}
else
{
// Linear search of all
idx = 0;
}
// Search forwards for the referent
for (int i = idx; i < refCache.size(); ++i)
{
NamedReference reference = refCache.get(i);
if (referentAddr == reference.getObjectAddress())
{
if (!excludeFields.contains(reference.getName()))
{
return false;
}
}
else if (sorted)
{
// No more references with this address
break;
}
}
return true;
}
private int[] bfs(IProgressListener progressListener) throws SnapshotException
{
// number objects in the heap
final int numObjects = snapshot.getSnapshotInfo().getNumberOfObjects();
final boolean skipReferences = excludeMap != null; // should some paths
// be excluded?
// used to store the parent of each object during the BFS
int[] parent = new int[numObjects];
Arrays.fill(parent, NOT_VISITED);
// use boolean[numObjects] instead of SetInt, as it is faster to check
boolean[] toBeChecked = new boolean[numObjects];
int count = 0; // the number of distinct objects whose paths should be
// calculated
for (int i : objectIds)
{
if (!toBeChecked[i]) count++;
toBeChecked[i] = true;
}
// use first-in-first-out to get the shortest paths
QueueInt fifo = new QueueInt(numObjects / 8);
// initially queue all GC roots
int[] gcRoots = snapshot.getGCRoots();
for (int root : gcRoots)
{
fifo.put(root);
parent[root] = NO_PARENT;
}
// used for the progress listener
int countVisitedObjects = 0;
final int steps = 1000;
int reportFrequency = Math.max(10, (numObjects + steps - 1) / steps);
progressListener.beginTask(Messages.MultiplePathsFromGCRootsComputerImpl_FindingPaths, steps);
// Used for performance
List<NamedReference>refCache = new ArrayList<NamedReference>();
// loop until the queue is empty, or all necessary paths are found
while (fifo.size() > 0 && count > 0)
{
int objectId = fifo.get();
// was some of the objects of interest reached?
if (toBeChecked[objectId])
{
count--; // reduce the remaining work
}
// queue any unprocessed referenced object
int[] outbound = outboundIndex.get(objectId);
refCache.clear();
for (int child : outbound)
{
if (parent[child] == NOT_VISITED)
{
if (skipReferences)
{
if (refersOnlyThroughExcluded(objectId, child, refCache)) continue;
}
parent[child] = objectId;
fifo.put(child);
}
}
countVisitedObjects++;
if (countVisitedObjects % reportFrequency == 0)
{
if (progressListener.isCanceled()) throw new IProgressListener.OperationCanceledException();
progressListener.worked(1);
}
}
progressListener.done();
return parent;
}
/*
* Returns the shortest path to an object, using the stored parent of every
* needed object calculated during a BFS
*
* @param int objectId the object to which a path should be calculated
*
* @param int[] parent an array, result of a BSF, which keeps a parent[i] is
* the parent of the object with index i, as calculated during the BFS
*
* @return int[] the shortest path from a GC root. The object of interest is
* at index 0, the GC root at index length-1
*/
private int[] getPathFromBFS(int objectId, int[] parent)
{
// check if the object wasn't reached at all. This may happen if some
// paths are excluded
if (parent[objectId] == NOT_VISITED) return null;
ArrayInt path = new ArrayInt();
while (objectId != NO_PARENT)
{
path.add(objectId);
objectId = parent[objectId];
}
return path.toArray();
}
/**
* Used for sorting {@link ObjectReference} by address.
*/
private static final class CompObjectReference implements Comparator<ObjectReference>
{
@Override
public int compare(ObjectReference o1, ObjectReference o2)
{
return Long.compare(o1.getObjectAddress(), o2.getObjectAddress());
}
static CompObjectReference INSTANCE = new CompObjectReference();
}
}