26 package sun.tools.jmap; |
26 package sun.tools.jmap; |
27 |
27 |
28 import java.io.File; |
28 import java.io.File; |
29 import java.io.IOException; |
29 import java.io.IOException; |
30 import java.io.InputStream; |
30 import java.io.InputStream; |
|
31 import java.io.UnsupportedEncodingException; |
31 |
32 |
32 import com.sun.tools.attach.VirtualMachine; |
33 import com.sun.tools.attach.VirtualMachine; |
33 import com.sun.tools.attach.AttachNotSupportedException; |
34 import com.sun.tools.attach.AttachNotSupportedException; |
34 import sun.tools.attach.HotSpotVirtualMachine; |
35 import sun.tools.attach.HotSpotVirtualMachine; |
35 import jdk.internal.vm.agent.spi.ToolProvider; |
|
36 import jdk.internal.vm.agent.spi.ToolProviderFinder; |
|
37 |
36 |
38 /* |
37 /* |
39 * This class is the main class for the JMap utility. It parses its arguments |
38 * This class is the main class for the JMap utility. It parses its arguments |
40 * and decides if the command should be satisfied using the VM attach mechanism |
39 * and decides if the command should be satisfied using the VM attach mechanism |
41 * or an SA tool. At this time the only option that uses the VM attach mechanism |
40 * or an SA tool. At this time the only option that uses the VM attach mechanism |
42 * is the -dump option to get a heap dump of a running application. All other |
41 * is the -dump option to get a heap dump of a running application. All other |
43 * options are mapped to SA tools. |
42 * options are mapped to SA tools. |
44 */ |
43 */ |
45 public class JMap { |
44 public class JMap { |
46 |
45 |
47 // Options handled by the attach mechanism |
|
48 private static String HISTO_OPTION = "-histo"; |
|
49 private static String LIVE_HISTO_OPTION = "-histo:live"; |
|
50 private static String DUMP_OPTION_PREFIX = "-dump:"; |
|
51 |
|
52 // These options imply the use of a SA tool |
|
53 private static String SA_TOOL_OPTIONS = |
|
54 "-heap|-heap:format=b|-clstats|-finalizerinfo"; |
|
55 |
|
56 // The -F (force) option is currently not passed through to SA |
|
57 private static String FORCE_SA_OPTION = "-F"; |
|
58 |
|
59 // Default option (if nothing provided) |
|
60 private static String DEFAULT_OPTION = "-pmap"; |
|
61 |
|
62 public static void main(String[] args) throws Exception { |
46 public static void main(String[] args) throws Exception { |
63 if (args.length == 0) { |
47 if (args.length == 0) { |
64 usage(1); // no arguments |
48 usage(1); // no arguments |
65 } |
49 } |
66 |
50 |
67 // used to indicate if we should use SA |
51 checkForUnsupportedOptions(args); |
68 boolean useSA = false; |
52 |
69 |
53 // the chosen option |
70 // the chosen option (-heap, -dump:*, ... ) |
|
71 String option = null; |
54 String option = null; |
72 |
55 |
73 // First iterate over the options (arguments starting with -). There should be |
56 // First iterate over the options (arguments starting with -). There should be |
74 // one (but maybe two if -F is also used). |
57 // one. |
75 int optionCount = 0; |
58 int optionCount = 0; |
76 while (optionCount < args.length) { |
59 while (optionCount < args.length) { |
77 String arg = args[optionCount]; |
60 String arg = args[optionCount]; |
78 if (!arg.startsWith("-")) { |
61 if (!arg.startsWith("-")) { |
79 break; |
62 break; |
80 } |
63 } |
81 if (arg.equals("-help") || arg.equals("-h")) { |
64 if (arg.equals("-help") || arg.equals("-h")) { |
82 usage(0); |
65 usage(0); |
83 } else if (arg.equals(FORCE_SA_OPTION)) { |
|
84 useSA = true; |
|
85 } else { |
66 } else { |
86 if (option != null) { |
67 if (option != null) { |
87 usage(1); // option already specified |
68 usage(1); // option already specified |
88 } |
69 } |
89 option = arg; |
70 option = arg; |
91 optionCount++; |
72 optionCount++; |
92 } |
73 } |
93 |
74 |
94 // if no option provided then use default. |
75 // if no option provided then use default. |
95 if (option == null) { |
76 if (option == null) { |
96 option = DEFAULT_OPTION; |
77 usage(0); |
97 } |
78 } |
98 if (option.matches(SA_TOOL_OPTIONS)) { |
79 |
99 useSA = true; |
80 // Next we check the parameter count. |
100 } |
|
101 |
|
102 // Next we check the parameter count. For the SA tools there are |
|
103 // one or two parameters. For the built-in -dump option there is |
|
104 // only one parameter (the process-id) |
|
105 int paramCount = args.length - optionCount; |
81 int paramCount = args.length - optionCount; |
106 if (paramCount == 0 || paramCount > 2) { |
82 if (paramCount != 1) { |
107 usage(1); |
83 usage(1); |
108 } |
84 } |
109 |
85 |
110 if (optionCount == 0 || paramCount != 1) { |
86 String pid = args[1]; |
111 useSA = true; |
87 // Here we handle the built-in options |
|
88 // As more options are added we should create an abstract tool class and |
|
89 // have a table to map the options |
|
90 if (option.equals("-histo")) { |
|
91 histo(pid, ""); |
|
92 } else if (option.startsWith("-histo:")) { |
|
93 histo(pid, option.substring("-histo:".length())); |
|
94 } else if (option.startsWith("-dump:")) { |
|
95 dump(pid, option.substring("-dump:".length())); |
|
96 } else if (option.equals("-finalizerinfo")) { |
|
97 executeCommandForPid(pid, "jcmd", "GC.finalizer_info"); |
|
98 } else if (option.equals("-clstats")) { |
|
99 executeCommandForPid(pid, "jcmd", "GC.class_stats"); |
112 } else { |
100 } else { |
113 // the parameter for the -dump option is a process-id. |
101 usage(1); |
114 // If it doesn't parse to a number then it must be SA |
102 } |
115 // debug server |
103 } |
116 if (!args[optionCount].matches("[0-9]+")) { |
104 |
117 useSA = true; |
105 private static void executeCommandForPid(String pid, String command, Object ... args) |
118 } |
106 throws AttachNotSupportedException, IOException, |
119 } |
107 UnsupportedEncodingException { |
120 |
108 VirtualMachine vm = VirtualMachine.attach(pid); |
121 |
109 |
122 // at this point we know if we are executing an SA tool or a built-in |
110 // Cast to HotSpotVirtualMachine as this is an |
123 // option. |
111 // implementation specific method. |
124 |
112 HotSpotVirtualMachine hvm = (HotSpotVirtualMachine) vm; |
125 if (useSA) { |
113 try (InputStream in = hvm.executeCommand(command, args)) { |
126 // parameters (<pid> or <exe> <core>) |
114 // read to EOF and just print output |
127 String params[] = new String[paramCount]; |
115 byte b[] = new byte[256]; |
128 for (int i=optionCount; i<args.length; i++ ){ |
116 int n; |
129 params[i-optionCount] = args[i]; |
117 do { |
130 } |
118 n = in.read(b); |
131 runTool(option, params); |
119 if (n > 0) { |
132 |
120 String s = new String(b, 0, n, "UTF-8"); |
133 } else { |
121 System.out.print(s); |
134 String pid = args[1]; |
122 } |
135 // Here we handle the built-in options |
123 } while (n > 0); |
136 // As more options are added we should create an abstract tool class and |
124 } |
137 // have a table to map the options |
125 vm.detach(); |
138 if (option.equals(HISTO_OPTION)) { |
126 } |
139 histo(pid, false); |
127 |
140 } else if (option.equals(LIVE_HISTO_OPTION)) { |
128 private static void histo(String pid, String options) |
141 histo(pid, true); |
129 throws AttachNotSupportedException, IOException, |
142 } else if (option.startsWith(DUMP_OPTION_PREFIX)) { |
130 UnsupportedEncodingException { |
143 dump(pid, option); |
131 String liveopt = "-all"; |
144 } else { |
132 if (options.equals("") || options.equals("all")) { |
145 usage(1); |
133 // pass |
146 } |
134 } |
147 } |
135 else if (options.equals("live")) { |
148 } |
136 liveopt = "-live"; |
149 |
137 } |
150 // Invoke SA tool with the given arguments |
138 else { |
151 private static void runTool(String option, String args[]) throws Exception { |
139 usage(1); |
152 String[][] tools = { |
140 } |
153 { "-pmap", "pmap" }, |
141 |
154 { "-heap", "heapSummary" }, |
142 // inspectHeap is not the same as jcmd GC.class_histogram |
155 { "-heap:format=b", "heapDumper" }, |
143 executeCommandForPid(pid, "inspectheap", liveopt); |
156 { "-histo", "objectHistogram" }, |
144 } |
157 { "-clstats", "classLoaderStats" }, |
145 |
158 { "-finalizerinfo", "finalizerInfo" }, |
146 private static void dump(String pid, String options) |
159 }; |
147 throws AttachNotSupportedException, IOException, |
160 |
148 UnsupportedEncodingException { |
161 String name = null; |
149 |
162 |
150 String subopts[] = options.split(","); |
163 // -dump option needs to be handled in a special way |
151 String filename = null; |
164 if (option.startsWith(DUMP_OPTION_PREFIX)) { |
152 String liveopt = "-all"; |
165 // first check that the option can be parsed |
153 |
166 String fn = parseDumpOptions(option); |
154 for (int i = 0; i < subopts.length; i++) { |
167 if (fn == null) { |
155 String subopt = subopts[i]; |
168 usage(1); |
156 if (subopt.equals("live")) { |
169 } |
157 liveopt = "-live"; |
170 |
158 } else if (subopt.startsWith("file=")) { |
171 // tool for heap dumping |
159 // file=<file> - check that <file> is specified |
172 name = "heapDumper"; |
160 if (subopt.length() > 5) { |
173 |
161 filename = subopt.substring(5); |
174 // HeapDumper -f <file> |
|
175 args = prepend(fn, args); |
|
176 args = prepend("-f", args); |
|
177 } else { |
|
178 int i=0; |
|
179 while (i < tools.length) { |
|
180 if (option.equals(tools[i][0])) { |
|
181 name = tools[i][1]; |
|
182 break; |
|
183 } |
162 } |
184 i++; |
163 } |
185 } |
164 } |
186 } |
165 |
187 if (name == null) { |
|
188 usage(1); // no mapping to tool |
|
189 } |
|
190 |
|
191 // Tool not available on this platform. |
|
192 ToolProvider tool = ToolProviderFinder.find(name); |
|
193 if (tool == null) { |
|
194 usage(1); |
|
195 } |
|
196 |
|
197 // invoke the main method with the arguments |
|
198 tool.run(args); |
|
199 } |
|
200 |
|
201 private static final String LIVE_OBJECTS_OPTION = "-live"; |
|
202 private static final String ALL_OBJECTS_OPTION = "-all"; |
|
203 private static void histo(String pid, boolean live) throws IOException { |
|
204 VirtualMachine vm = attach(pid); |
|
205 InputStream in = ((HotSpotVirtualMachine)vm). |
|
206 heapHisto(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION); |
|
207 drain(vm, in); |
|
208 } |
|
209 |
|
210 private static void dump(String pid, String options) throws IOException { |
|
211 // parse the options to get the dump filename |
|
212 String filename = parseDumpOptions(options); |
|
213 if (filename == null) { |
166 if (filename == null) { |
214 usage(1); // invalid options or no filename |
167 usage(1); // invalid options or no filename |
215 } |
168 } |
216 |
169 |
217 // get the canonical path - important to avoid just passing |
170 // get the canonical path - important to avoid just passing |
218 // a "heap.bin" and having the dump created in the target VM |
171 // a "heap.bin" and having the dump created in the target VM |
219 // working directory rather than the directory where jmap |
172 // working directory rather than the directory where jmap |
220 // is executed. |
173 // is executed. |
221 filename = new File(filename).getCanonicalPath(); |
174 filename = new File(filename).getCanonicalPath(); |
222 |
175 // dumpHeap is not the same as jcmd GC.heap_dump |
223 // dump live objects only or not |
176 executeCommandForPid(pid, "dumpheap", filename, liveopt); |
224 boolean live = isDumpLiveObjects(options); |
177 } |
225 |
178 |
226 VirtualMachine vm = attach(pid); |
179 private static void checkForUnsupportedOptions(String[] args) { |
227 System.out.println("Dumping heap to " + filename + " ..."); |
180 // Check arguments for -F, -m, and non-numeric value |
228 InputStream in = ((HotSpotVirtualMachine)vm). |
181 // and warn the user that SA is not supported anymore |
229 dumpHeap((Object)filename, |
182 |
230 (live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION)); |
183 int paramCount = 0; |
231 drain(vm, in); |
184 |
232 } |
185 for (String s : args) { |
233 |
186 if (s.equals("-F")) { |
234 // Parse the options to the -dump option. Valid options are format=b and |
187 SAOptionError("-F option used"); |
235 // file=<file>. Returns <file> if provided. Returns null if <file> not |
188 } |
236 // provided, or invalid option. |
189 |
237 private static String parseDumpOptions(String arg) { |
190 if (s.equals("-heap")) { |
238 assert arg.startsWith(DUMP_OPTION_PREFIX); |
191 SAOptionError("-heap option used"); |
239 |
192 } |
240 String filename = null; |
193 |
241 |
194 /* Reimplemented using jcmd, output format is different |
242 // options are separated by comma (,) |
195 from original one |
243 String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(","); |
196 |
244 |
197 if (s.equals("-clstats")) { |
245 for (int i=0; i<options.length; i++) { |
198 warnSA("-clstats option used"); |
246 String option = options[i]; |
199 } |
247 |
200 |
248 if (option.equals("format=b")) { |
201 if (s.equals("-finalizerinfo")) { |
249 // ignore format (not needed at this time) |
202 warnSA("-finalizerinfo option used"); |
250 } else if (option.equals("live")) { |
203 } |
251 // a valid suboption |
204 */ |
252 } else { |
205 |
253 |
206 if (! s.startsWith("-")) { |
254 // file=<file> - check that <file> is specified |
207 if (! s.matches("[0-9]+")) { |
255 if (option.startsWith("file=")) { |
208 SAOptionError("non PID argument"); |
256 filename = option.substring(5); |
|
257 if (filename.length() == 0) { |
|
258 return null; |
|
259 } |
|
260 } else { |
|
261 return null; // option not recognized |
|
262 } |
209 } |
263 } |
210 paramCount += 1; |
264 } |
211 } |
265 return filename; |
212 } |
266 } |
213 |
267 |
214 if (paramCount > 1) { |
268 private static boolean isDumpLiveObjects(String arg) { |
215 SAOptionError("More than one non-option argument"); |
269 // options are separated by comma (,) |
216 } |
270 String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(","); |
217 } |
271 for (String suboption : options) { |
218 |
272 if (suboption.equals("live")) { |
219 private static void SAOptionError(String msg) { |
273 return true; |
220 System.err.println("Error: " + msg); |
274 } |
221 System.err.println("Cannot connect to core dump or remote debug server. Use jhsdb jmap instead"); |
275 } |
222 System.exit(1); |
276 return false; |
|
277 } |
|
278 |
|
279 // Attach to <pid>, existing if we fail to attach |
|
280 private static VirtualMachine attach(String pid) { |
|
281 try { |
|
282 return VirtualMachine.attach(pid); |
|
283 } catch (Exception x) { |
|
284 String msg = x.getMessage(); |
|
285 if (msg != null) { |
|
286 System.err.println(pid + ": " + msg); |
|
287 } else { |
|
288 x.printStackTrace(); |
|
289 } |
|
290 if ((x instanceof AttachNotSupportedException) && haveSA()) { |
|
291 System.err.println("The -F option can be used when the " + |
|
292 "target process is not responding"); |
|
293 } |
|
294 System.exit(1); |
|
295 return null; // keep compiler happy |
|
296 } |
|
297 } |
|
298 |
|
299 // Read the stream from the target VM until EOF, then detach |
|
300 private static void drain(VirtualMachine vm, InputStream in) throws IOException { |
|
301 // read to EOF and just print output |
|
302 byte b[] = new byte[256]; |
|
303 int n; |
|
304 do { |
|
305 n = in.read(b); |
|
306 if (n > 0) { |
|
307 String s = new String(b, 0, n, "UTF-8"); |
|
308 System.out.print(s); |
|
309 } |
|
310 } while (n > 0); |
|
311 in.close(); |
|
312 vm.detach(); |
|
313 } |
|
314 |
|
315 // return a new string array with arg as the first element |
|
316 private static String[] prepend(String arg, String args[]) { |
|
317 String[] newargs = new String[args.length+1]; |
|
318 newargs[0] = arg; |
|
319 System.arraycopy(args, 0, newargs, 1, args.length); |
|
320 return newargs; |
|
321 } |
|
322 |
|
323 // returns true if SA is available |
|
324 private static boolean haveSA() { |
|
325 return ToolProviderFinder.find("heapSummary") != null; |
|
326 } |
223 } |
327 |
224 |
328 // print usage message |
225 // print usage message |
329 private static void usage(int exit) { |
226 private static void usage(int exit) { |
330 System.err.println("Usage:"); |
227 System.err.println("Usage:"); |
331 if (haveSA()) { |
228 System.err.println(" jmap -clstats <pid>"); |
332 System.err.println(" jmap [option] <pid>"); |
229 System.err.println(" to connect to running process and print class loader statistics"); |
333 System.err.println(" (to connect to running process)"); |
230 System.err.println(" jmap -finalizerinfo <pid>"); |
334 System.err.println(" jmap [option] <executable <core>"); |
231 System.err.println(" to connect to running process and print information on objects awaiting finalization"); |
335 System.err.println(" (to connect to a core file)"); |
232 System.err.println(" jmap -histo[:live] <pid>"); |
336 System.err.println(" jmap [option] [server_id@]<remote server IP or hostname>"); |
233 System.err.println(" to connect to running process and print histogram of java object heap"); |
337 System.err.println(" (to connect to remote debug server)"); |
234 System.err.println(" if the \"live\" suboption is specified, only count live objects"); |
338 System.err.println(""); |
235 System.err.println(" jmap -dump:<dump-options> <pid>"); |
339 System.err.println("where <option> is one of:"); |
236 System.err.println(" to connect to running process and dump java heap"); |
340 System.err.println(" <none> to print same info as Solaris pmap"); |
237 System.err.println(""); |
341 System.err.println(" -heap to print java heap summary"); |
238 System.err.println(" dump-options:"); |
342 System.err.println(" -histo[:live] to print histogram of java object heap; if the \"live\""); |
239 System.err.println(" live dump only live objects; if not specified,"); |
343 System.err.println(" suboption is specified, only count live objects"); |
240 System.err.println(" all objects in the heap are dumped."); |
344 System.err.println(" -clstats to print class loader statistics"); |
241 System.err.println(" format=b binary format"); |
345 System.err.println(" -finalizerinfo to print information on objects awaiting finalization"); |
242 System.err.println(" file=<file> dump heap to <file>"); |
346 System.err.println(" -dump:<dump-options> to dump java heap in hprof binary format"); |
243 System.err.println(""); |
347 System.err.println(" dump-options:"); |
244 System.err.println(" Example: jmap -dump:live,format=b,file=heap.bin <pid>"); |
348 System.err.println(" live dump only live objects; if not specified,"); |
|
349 System.err.println(" all objects in the heap are dumped."); |
|
350 System.err.println(" format=b binary format"); |
|
351 System.err.println(" file=<file> dump heap to <file>"); |
|
352 System.err.println(" Example: jmap -dump:live,format=b,file=heap.bin <pid>"); |
|
353 System.err.println(" -F force. Use with -dump:<dump-options> <pid> or -histo"); |
|
354 System.err.println(" to force a heap dump or histogram when <pid> does not"); |
|
355 System.err.println(" respond. The \"live\" suboption is not supported"); |
|
356 System.err.println(" in this mode."); |
|
357 System.err.println(" -h | -help to print this help message"); |
|
358 System.err.println(" -J<flag> to pass <flag> directly to the runtime system"); |
|
359 } else { |
|
360 System.err.println(" jmap -histo <pid>"); |
|
361 System.err.println(" (to connect to running process and print histogram of java object heap"); |
|
362 System.err.println(" jmap -dump:<dump-options> <pid>"); |
|
363 System.err.println(" (to connect to running process and dump java heap)"); |
|
364 System.err.println(""); |
|
365 System.err.println(" dump-options:"); |
|
366 System.err.println(" format=b binary default"); |
|
367 System.err.println(" file=<file> dump heap to <file>"); |
|
368 System.err.println(""); |
|
369 System.err.println(" Example: jmap -dump:format=b,file=heap.bin <pid>"); |
|
370 } |
|
371 |
|
372 System.exit(exit); |
245 System.exit(exit); |
373 } |
246 } |
374 } |
247 } |