001: /*
002: * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jmap;
027:
028: import java.lang.reflect.Method;
029: import java.io.File;
030: import java.io.IOException;
031: import java.io.InputStream;
032:
033: import com.sun.tools.attach.VirtualMachine;
034: import com.sun.tools.attach.AttachNotSupportedException;
035: import sun.tools.attach.HotSpotVirtualMachine;
036:
037: /*
038: * This class is the main class for the JMap utility. It parses its arguments
039: * and decides if the command should be satisifed using the VM attach mechanism
040: * or an SA tool. At this time the only option that uses the VM attach mechanism
041: * is the -dump option to get a heap dump of a running application. All other
042: * options are mapped to SA tools.
043: */
044: public class JMap {
045:
046: // Options handled by the attach mechanism
047: private static String HISTO_OPTION = "-histo";
048: private static String LIVE_HISTO_OPTION = "-histo:live";
049: private static String DUMP_OPTION_PREFIX = "-dump:";
050:
051: // These options imply the use of a SA tool
052: private static String SA_TOOL_OPTIONS = "-heap|-heap:format=b|-permstat|-finalizerinfo";
053:
054: // The -F (force) option is currently not passed through to SA
055: private static String FORCE_SA_OPTION = "-F";
056:
057: // Default option (if nothing provided)
058: private static String DEFAULT_OPTION = "-heap";
059:
060: public static void main(String[] args) throws Exception {
061: if (args.length == 0) {
062: usage(); // no arguments
063: }
064:
065: // used to indicate if we should use SA
066: boolean useSA = false;
067:
068: // the chosen option (-heap, -dump:*, ... )
069: String option = null;
070:
071: // First iterate over the options (arguments starting with -). There should be
072: // one (but maybe two if -F is also used).
073: int optionCount = 0;
074: while (optionCount < args.length) {
075: String arg = args[optionCount];
076: if (!arg.startsWith("-")) {
077: break;
078: }
079: if (arg.equals(FORCE_SA_OPTION)) {
080: useSA = true;
081: } else {
082: if (option != null) {
083: usage(); // option already specified
084: }
085: option = arg;
086: }
087: optionCount++;
088: }
089:
090: // if no option provided then use default.
091: if (option == null) {
092: option = DEFAULT_OPTION;
093: }
094: if (option.matches(SA_TOOL_OPTIONS)) {
095: useSA = true;
096: }
097:
098: // Next we check the parameter count. For the SA tools there are
099: // one or two parameters. For the built-in -dump option there is
100: // only one parameter (the process-id)
101: int paramCount = args.length - optionCount;
102: if (paramCount == 0 || paramCount > 2) {
103: usage();
104: }
105:
106: if (optionCount == 0 || paramCount != 1) {
107: useSA = true;
108: } else {
109: // the parameter for the -dump option is a process-id.
110: // If it doesn't parse to a number then it must be SA
111: // debug server
112: if (!args[optionCount].matches("[0-9]+")) {
113: useSA = true;
114: }
115: }
116:
117: // at this point we know if we are executing an SA tool or a built-in
118: // option.
119:
120: if (useSA) {
121: // parameters (<pid> or <exe> <core>)
122: String params[] = new String[paramCount];
123: for (int i = optionCount; i < args.length; i++) {
124: params[i - optionCount] = args[i];
125: }
126: runTool(option, params);
127:
128: } else {
129: String pid = args[1];
130: // Here we handle the built-in options
131: // As more options are added we should create an abstract tool class and
132: // have a table to map the options
133: if (option.equals(HISTO_OPTION)) {
134: histo(pid, false);
135: } else if (option.equals(LIVE_HISTO_OPTION)) {
136: histo(pid, true);
137: } else if (option.startsWith(DUMP_OPTION_PREFIX)) {
138: dump(pid, option);
139: } else {
140: usage();
141: }
142: }
143: }
144:
145: // Invoke SA tool with the given arguments
146: private static void runTool(String option, String args[])
147: throws Exception {
148: String[][] tools = {
149: { "-heap", "sun.jvm.hotspot.tools.HeapSummary" },
150: { "-heap:format=b", "sun.jvm.hotspot.tools.HeapDumper" },
151: { "-histo", "sun.jvm.hotspot.tools.ObjectHistogram" },
152: { "-permstat", "sun.jvm.hotspot.tools.PermStat" },
153: { "-finalizerinfo",
154: "sun.jvm.hotspot.tools.FinalizerInfo" }, };
155:
156: String tool = null;
157:
158: // -dump option needs to be handled in a special way
159: if (option.startsWith(DUMP_OPTION_PREFIX)) {
160: // first check that the option can be parsed
161: String fn = parseDumpOptions(option);
162: if (fn == null)
163: usage();
164:
165: // tool for heap dumping
166: tool = "sun.jvm.hotspot.tools.HeapDumper";
167:
168: // HeapDumper -f <file>
169: args = prepend(fn, args);
170: args = prepend("-f", args);
171: } else {
172: int i = 0;
173: while (i < tools.length) {
174: if (option.equals(tools[i][0])) {
175: tool = tools[i][1];
176: break;
177: }
178: i++;
179: }
180: }
181: if (tool == null) {
182: usage(); // no mapping to tool
183: }
184:
185: // Tool not available on this platform.
186: Class<?> c = loadClass(tool);
187: if (c == null) {
188: usage();
189: }
190:
191: // invoke the main method with the arguments
192: Class[] argTypes = { String[].class };
193: Method m = c.getDeclaredMethod("main", argTypes);
194:
195: Object[] invokeArgs = { args };
196: m.invoke(null, invokeArgs);
197: }
198:
199: // loads the given class using the system class loader
200: private static Class loadClass(String name) {
201: //
202: // We specify the system clas loader so as to cater for development
203: // environments where this class is on the boot class path but sa-jdi.jar
204: // is on the system class path. Once the JDK is deployed then both
205: // tools.jar and sa-jdi.jar are on the system class path.
206: //
207: try {
208: return Class.forName(name, true, ClassLoader
209: .getSystemClassLoader());
210: } catch (Exception x) {
211: }
212: return null;
213: }
214:
215: private static final String LIVE_OBJECTS_OPTION = "-live";
216: private static final String ALL_OBJECTS_OPTION = "-all";
217:
218: private static void histo(String pid, boolean live)
219: throws IOException {
220: VirtualMachine vm = attach(pid);
221: InputStream in = ((HotSpotVirtualMachine) vm)
222: .heapHisto(live ? LIVE_OBJECTS_OPTION
223: : ALL_OBJECTS_OPTION);
224: drain(vm, in);
225: }
226:
227: private static void dump(String pid, String options)
228: throws IOException {
229: // parse the options to get the dump filename
230: String filename = parseDumpOptions(options);
231: if (filename == null) {
232: usage(); // invalid options or no filename
233: }
234:
235: // get the canonical path - important to avoid just passing
236: // a "heap.bin" and having the dump created in the target VM
237: // working directory rather than the directory where jmap
238: // is executed.
239: filename = new File(filename).getCanonicalPath();
240:
241: // dump live objects only or not
242: boolean live = isDumpLiveObjects(options);
243:
244: VirtualMachine vm = attach(pid);
245: System.out.println("Dumping heap to " + filename + " ...");
246: InputStream in = ((HotSpotVirtualMachine) vm).dumpHeap(
247: (Object) filename, (live ? LIVE_OBJECTS_OPTION
248: : ALL_OBJECTS_OPTION));
249: drain(vm, in);
250: }
251:
252: // Parse the options to the -dump option. Valid options are format=b and
253: // file=<file>. Returns <file> if provided. Returns null if <file> not
254: // provided, or invalid option.
255: private static String parseDumpOptions(String arg) {
256: assert arg.startsWith(DUMP_OPTION_PREFIX);
257:
258: String filename = null;
259:
260: // options are separated by comma (,)
261: String options[] = arg.substring(DUMP_OPTION_PREFIX.length())
262: .split(",");
263:
264: for (int i = 0; i < options.length; i++) {
265: String option = options[i];
266:
267: if (option.equals("format=b")) {
268: // ignore format (not needed at this time)
269: } else if (option.equals("live")) {
270: // a valid suboption
271: } else {
272:
273: // file=<file> - check that <file> is specified
274: if (option.startsWith("file=")) {
275: filename = option.substring(5);
276: if (filename.length() == 0) {
277: return null;
278: }
279: } else {
280: return null; // option not recognized
281: }
282: }
283: }
284: return filename;
285: }
286:
287: private static boolean isDumpLiveObjects(String arg) {
288: // options are separated by comma (,)
289: String options[] = arg.substring(DUMP_OPTION_PREFIX.length())
290: .split(",");
291: for (String suboption : options) {
292: if (suboption.equals("live")) {
293: return true;
294: }
295: }
296: return false;
297: }
298:
299: // Attach to <pid>, existing if we fail to attach
300: private static VirtualMachine attach(String pid) {
301: try {
302: return VirtualMachine.attach(pid);
303: } catch (Exception x) {
304: String msg = x.getMessage();
305: if (msg != null) {
306: System.err.println(pid + ": " + msg);
307: } else {
308: x.printStackTrace();
309: }
310: if ((x instanceof AttachNotSupportedException) && haveSA()) {
311: System.err
312: .println("The -F option can be used when the "
313: + "target process is not responding");
314: }
315: System.exit(1);
316: return null; // keep compiler happy
317: }
318: }
319:
320: // Read the stream from the target VM until EOF, then detach
321: private static void drain(VirtualMachine vm, InputStream in)
322: throws IOException {
323: // read to EOF and just print output
324: byte b[] = new byte[256];
325: int n;
326: do {
327: n = in.read(b);
328: if (n > 0) {
329: String s = new String(b, 0, n, "UTF-8");
330: System.out.print(s);
331: }
332: } while (n > 0);
333: in.close();
334: vm.detach();
335: }
336:
337: // return a new string array with arg as the first element
338: private static String[] prepend(String arg, String args[]) {
339: String[] newargs = new String[args.length + 1];
340: newargs[0] = arg;
341: System.arraycopy(args, 0, newargs, 1, args.length);
342: return newargs;
343: }
344:
345: // returns true if SA is available
346: private static boolean haveSA() {
347: Class c = loadClass("sun.jvm.hotspot.tools.HeapSummary");
348: return (c != null);
349: }
350:
351: // print usage message
352: private static void usage() {
353: System.out.println("Usage:");
354: if (haveSA()) {
355: System.out.println(" jmap [option] <pid>");
356: System.out
357: .println(" (to connect to running process)");
358: System.out.println(" jmap [option] <executable <core>");
359: System.out.println(" (to connect to a core file)");
360: System.out
361: .println(" jmap [option] [server_id@]<remote server IP or hostname>");
362: System.out
363: .println(" (to connect to remote debug server)");
364: System.out.println("");
365: System.out.println("where <option> is one of:");
366: System.out
367: .println(" <none> to print same info as Solaris pmap");
368: System.out
369: .println(" -heap to print java heap summary");
370: System.out
371: .println(" -histo[:live] to print histogram of java object heap; if the \"live\"");
372: System.out
373: .println(" suboption is specified, only count live objects");
374: System.out
375: .println(" -permstat to print permanent generation statistics");
376: System.out
377: .println(" -finalizerinfo to print information on objects awaiting finalization");
378: System.out
379: .println(" -dump:<dump-options> to dump java heap in hprof binary format");
380: System.out
381: .println(" dump-options:");
382: System.out
383: .println(" live dump only live objects; if not specified,");
384: System.out
385: .println(" all objects in the heap are dumped.");
386: System.out
387: .println(" format=b binary format");
388: System.out
389: .println(" file=<file> dump heap to <file>");
390: System.out
391: .println(" Example: jmap -dump:live,format=b,file=heap.bin <pid>");
392: System.out
393: .println(" -F force. Use with -dump:<dump-options> <pid> or -histo");
394: System.out
395: .println(" to force a heap dump or histogram when <pid> does not");
396: System.out
397: .println(" respond. The \"live\" suboption is not supported");
398: System.out
399: .println(" in this mode.");
400: System.out
401: .println(" -h | -help to print this help message");
402: System.out
403: .println(" -J<flag> to pass <flag> directly to the runtime system");
404: } else {
405: System.out.println(" jmap -histo <pid>");
406: System.out
407: .println(" (to connect to running process and print histogram of java object heap");
408: System.out.println(" jmap -dump:<dump-options> <pid>");
409: System.out
410: .println(" (to connect to running process and dump java heap)");
411: System.out.println("");
412: System.out.println(" dump-options:");
413: System.out.println(" format=b binary default");
414: System.out
415: .println(" file=<file> dump heap to <file>");
416: System.out.println("");
417: System.out
418: .println(" Example: jmap -dump:format=b,file=heap.bin <pid>");
419: }
420:
421: System.exit(1);
422: }
423: }
|