1
18 package net.bull.javamelody.internal.model;
19
20 import java.io.InputStream;
21 import java.io.Serializable;
22 import java.security.CodeSource;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Scanner;
31
32
36 public class HeapHistogram implements Serializable {
37 private static final long serialVersionUID = 2163916067335213382L;
38
39 @SuppressWarnings("all")
40 private final List<ClassInfo> classes;
41 @SuppressWarnings("all")
42 private final List<ClassInfo> permGenClasses;
43 private final Date time;
44 private long totalHeapBytes;
45 private long totalHeapInstances;
46 private long totalPermGenBytes;
47 private long totalPermgenInstances;
48 private boolean sourceDisplayed;
49 private boolean deltaDisplayed;
50
51 public HeapHistogram(InputStream in, boolean jrockit) {
52 time = new Date();
53 final Scanner sc = new Scanner(in, "UTF-8");
54 final List<ClassInfo> classInfos = scan(sc, jrockit);
55
56 classes = new ArrayList<>();
57 permGenClasses = new ArrayList<>();
58
59 for (final ClassInfo classInfo : classInfos) {
60 if (classInfo.isPermGen()) {
61 permGenClasses.add(classInfo);
62 totalPermGenBytes += classInfo.getBytes();
63 totalPermgenInstances += classInfo.getInstancesCount();
64 } else {
65 classes.add(classInfo);
66 totalHeapBytes += classInfo.getBytes();
67 totalHeapInstances += classInfo.getInstancesCount();
68 }
69 if (!sourceDisplayed && classInfo.getSource() != null) {
70 sourceDisplayed = true;
71 }
72 }
73 if (!jrockit) {
74 sc.next("Total");
75 final long totalInstances = sc.nextLong();
76 final long totalBytes = sc.nextLong();
77 assert totalInstances == totalPermgenInstances + totalHeapInstances;
78 assert totalBytes == totalPermGenBytes + totalHeapBytes;
79 }
80 sort();
81 }
82
83 public void add(HeapHistogram second) {
84 final Map<String, ClassInfo> classesMap = new HashMap<>(1024);
85 final Map<String, ClassInfo> permGenMap = new HashMap<>(1024);
86 for (final ClassInfo classInfo : classes) {
87 addClassInfo(classInfo, classesMap);
88 }
89 for (final ClassInfo classInfo : permGenClasses) {
90 addClassInfo(classInfo, permGenMap);
91 }
92 for (final ClassInfo classInfo : second.getHeapHistogram()) {
93 addClassInfo(classInfo, classesMap);
94 }
95 for (final ClassInfo classInfo : second.getPermGenHistogram()) {
96 addClassInfo(classInfo, permGenMap);
97 }
98 totalHeapBytes += second.getTotalHeapBytes();
99 totalHeapInstances += second.getTotalHeapInstances();
100 totalPermGenBytes += second.getTotalPermGenBytes();
101 totalPermgenInstances += second.getTotalPermGenInstances();
102 classes.clear();
103 classes.addAll(classesMap.values());
104 permGenClasses.clear();
105 permGenClasses.addAll(permGenMap.values());
106 sort();
107 sourceDisplayed = sourceDisplayed || second.isSourceDisplayed();
108 }
109
110 private void addClassInfo(ClassInfo newClInfo, Map<String, ClassInfo> map) {
111 final ClassInfo oldClInfo = map.get(newClInfo.getName());
112 if (oldClInfo == null) {
113 map.put(newClInfo.getName(), newClInfo);
114 } else {
115 oldClInfo.add(newClInfo);
116 }
117 }
118
119 public Date getTime() {
120 return time;
121 }
122
123 public List<ClassInfo> getHeapHistogram() {
124 return Collections.unmodifiableList(classes);
125 }
126
127 public long getTotalHeapInstances() {
128 return totalHeapInstances;
129 }
130
131 public long getTotalHeapBytes() {
132 return totalHeapBytes;
133 }
134
135 public List<ClassInfo> getPermGenHistogram() {
136 return Collections.unmodifiableList(permGenClasses);
137 }
138
139 public long getTotalPermGenInstances() {
140 return totalPermgenInstances;
141 }
142
143 public long getTotalPermGenBytes() {
144 return totalPermGenBytes;
145 }
146
147 public boolean isSourceDisplayed() {
148 return sourceDisplayed;
149 }
150
151
155 @Deprecated
156 boolean isDeltaDisplayed() {
157 return deltaDisplayed;
158 }
159
160 private void sort() {
161 final Comparator<ClassInfo> classInfoReversedComparator = Collections
162 .reverseOrder(new ClassInfoComparator());
163 Collections.sort(permGenClasses, classInfoReversedComparator);
164 Collections.sort(classes, classInfoReversedComparator);
165 }
166
167 static final class ClassInfoComparator implements Comparator<ClassInfo>, Serializable {
168 private static final long serialVersionUID = 1L;
169
170
171 @Override
172 public int compare(ClassInfo classInfo1, ClassInfo classInfo2) {
173 return Long.compare(classInfo1.getBytes(), classInfo2.getBytes());
174 }
175 }
176
177 private void skipHeader(Scanner sc, boolean jrockit) {
178 final String nextLine = sc.nextLine();
179
180 if (nextLine.isEmpty()) {
181 sc.nextLine();
182 }
183 if (!jrockit) {
184 sc.skip("-+");
185 sc.nextLine();
186 }
187 }
188
189 private List<ClassInfo> scan(Scanner sc, boolean jrockit) {
190 final Map<String, ClassInfo> classInfoMap = new HashMap<>(1024);
191 sc.useRadix(10);
192 skipHeader(sc, jrockit);
193
194 final String nextLine;
195 if (jrockit) {
196 nextLine = "[0-9.]+%";
197 } else {
198 nextLine = "[0-9]+:";
199 }
200 while (sc.hasNext(nextLine)) {
201 final ClassInfo newClInfo = new ClassInfo(sc, jrockit);
202 addClassInfo(newClInfo, classInfoMap);
203 }
204 return new ArrayList<>(classInfoMap.values());
205 }
206
207
211 public static class ClassInfo implements Serializable {
212 private static final long serialVersionUID = 6283636454450216347L;
213 private static final Map<Character, String> ARRAY_TYPES = new HashMap<>();
214
215 static {
216 ARRAY_TYPES.put('Z', "boolean");
217 ARRAY_TYPES.put('C', "char");
218 ARRAY_TYPES.put('B', "byte");
219 ARRAY_TYPES.put('S', "short");
220 ARRAY_TYPES.put('I', "int");
221 ARRAY_TYPES.put('J', "long");
222 ARRAY_TYPES.put('F', "float");
223 ARRAY_TYPES.put('D', "double");
224 ARRAY_TYPES.put('L', "object");
225 }
226
227 private long instances;
228 private long bytes;
229 private final String jvmName;
230 private final String name;
231 private final boolean permGen;
232 private final String source;
233
234 ClassInfo(Scanner sc, boolean jrockit) {
235 super();
236
237 sc.next();
238 if (jrockit) {
239 bytes = parseLongWithK(sc.next());
240 instances = sc.nextLong();
241 } else {
242 instances = sc.nextLong();
243 bytes = sc.nextLong();
244 }
245 jvmName = sc.next();
246 permGen = jvmName.charAt(0) == '<';
247 name = convertJVMName();
248 source = findSource();
249 if (sc.hasNext("\\([a-zA-Z.@0-9-ea]*\\)")) {
250
251 sc.next();
252 }
253 }
254
255 void add(ClassInfo classInfo) {
256 assert getName().equals(classInfo.getName());
257 this.bytes += classInfo.getBytes();
258 this.instances += classInfo.getInstancesCount();
259 }
260
261 public String getName() {
262 return name;
263 }
264
265 public long getInstancesCount() {
266 return instances;
267 }
268
269 public long getBytes() {
270 return bytes;
271 }
272
273 boolean isPermGen() {
274 return permGen;
275 }
276
277 public String getSource() {
278 return source;
279 }
280
281 private String findSource() {
282
283 if (jvmName.endsWith("Klass>") || jvmName.startsWith("sun.reflect.")) {
284 return null;
285 }
286 try {
287 final Class<?> clazz = Class.forName(jvmName);
288 return findSource(clazz);
289 } catch (final LinkageError | ClassNotFoundException e) {
290
291
292
293
294 return null;
295 }
296 }
297
298 private static String findSource(Class<?> clazz) {
299 final CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
300 if (codeSource != null && codeSource.getLocation() != null) {
301 String src = codeSource.getLocation().toString();
302 if (src.startsWith("file:/")) {
303 src = src.substring("file:/".length());
304 } else if (src.startsWith("vfs:/")) {
305
306 src = src.substring("vfs:/".length());
307 } else if (src.startsWith("reference:file:/")) {
308
309 src = src.substring("reference:file:/".length());
310 }
311 if (src.endsWith(".jar") || src.endsWith(".war")) {
312 src = src.intern();
313 }
314 return src;
315 }
316 return null;
317 }
318
319 private String convertJVMName() {
320 String result;
321 final int index = jvmName.lastIndexOf('[');
322
323 if (index != -1) {
324 final char code = jvmName.charAt(index + 1);
325 if (code == 'L') {
326 result = jvmName.substring(index + 2, jvmName.length() - 1);
327 } else {
328 result = ARRAY_TYPES.get(code);
329 if (result == null) {
330 result = jvmName;
331 }
332 }
333 final StringBuilder sb = new StringBuilder(result);
334 for (int i = 0; i <= index; i++) {
335 sb.append("[]");
336 }
337 result = sb.toString();
338 } else {
339 result = jvmName;
340 }
341 return result.intern();
342 }
343
344 public static long parseLongWithK(String text) {
345 assert !text.isEmpty();
346 if (text.charAt(text.length() - 1) == 'k') {
347 String t = text.substring(0, text.length() - 1);
348 if (t.charAt(0) == '+') {
349 t = t.substring(1);
350 }
351 return 1024 * Long.parseLong(t);
352 }
353
354
355
356
357 return Long.parseLong(text);
358 }
359 }
360 }
361