1 /*
   2  * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.tools.jmap;
  27 
  28 import java.lang.reflect.Method;
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 
  33 import com.sun.tools.attach.VirtualMachine;
  34 import com.sun.tools.attach.AttachNotSupportedException;
  35 import sun.tools.attach.HotSpotVirtualMachine;
  36 
  37 /*
  38  * This class is the main class for the JMap utility. It parses its arguments
  39  * and decides if the command should be satisfied using the VM attach mechanism
  40  * or an SA tool. At this time the only option that uses the VM attach mechanism
  41  * is the -dump option to get a heap dump of a running application. All other
  42  * options are mapped to SA tools.
  43  */
  44 public class JMap {
  45 
  46     // Options handled by the attach mechanism
  47     private static String HISTO_OPTION = "-histo";
  48     private static String LIVE_HISTO_OPTION = "-histo:live";
  49     private static String DUMP_OPTION_PREFIX = "-dump:";
  50 
  51     // These options imply the use of a SA tool
  52     private static String SA_TOOL_OPTIONS =
  53       "-heap|-heap:format=b|-clstats|-finalizerinfo";
  54 
  55     // The -F (force) option is currently not passed through to SA
  56     private static String FORCE_SA_OPTION = "-F";
  57 
  58     // Default option (if nothing provided)
  59     private static String DEFAULT_OPTION = "-pmap";
  60 
  61     public static void main(String[] args) throws Exception {
  62         if (args.length == 0) {
  63             usage(); // no arguments
  64         }
  65 
  66         // used to indicate if we should use SA
  67         boolean useSA = false;
  68 
  69         // the chosen option (-heap, -dump:*, ... )
  70         String option = null;
  71 
  72         // First iterate over the options (arguments starting with -).  There should be
  73         // one (but maybe two if -F is also used).
  74         int optionCount = 0;
  75         while (optionCount < args.length) {
  76             String arg = args[optionCount];
  77             if (!arg.startsWith("-")) {
  78                 break;
  79             }
  80             if (arg.equals(FORCE_SA_OPTION)) {
  81                 useSA = true;
  82             } else {
  83                 if (option != null) {
  84                     usage();  // option already specified
  85                 }
  86                 option = arg;
  87             }
  88             optionCount++;
  89         }
  90 
  91         // if no option provided then use default.
  92         if (option == null) {
  93             option = DEFAULT_OPTION;
  94         }
  95         if (option.matches(SA_TOOL_OPTIONS)) {
  96             useSA = true;
  97         }
  98 
  99         // Next we check the parameter count. For the SA tools there are
 100         // one or two parameters. For the built-in -dump option there is
 101         // only one parameter (the process-id)
 102         int paramCount = args.length - optionCount;
 103         if (paramCount == 0 || paramCount > 2) {
 104             usage();
 105         }
 106 
 107         if (optionCount == 0 || paramCount != 1) {
 108             useSA = true;
 109         } else {
 110             // the parameter for the -dump option is a process-id.
 111             // If it doesn't parse to a number then it must be SA
 112             // debug server
 113             if (!args[optionCount].matches("[0-9]+")) {
 114                 useSA = true;
 115             }
 116         }
 117 
 118 
 119         // at this point we know if we are executing an SA tool or a built-in
 120         // option.
 121 
 122         if (useSA) {
 123             // parameters (<pid> or <exe> <core>)
 124             String params[] = new String[paramCount];
 125             for (int i=optionCount; i<args.length; i++ ){
 126                 params[i-optionCount] = args[i];
 127             }
 128             runTool(option, params);
 129 
 130         } else {
 131             String pid = args[1];
 132             // Here we handle the built-in options
 133             // As more options are added we should create an abstract tool class and
 134             // have a table to map the options
 135             if (option.equals(HISTO_OPTION)) {
 136                 histo(pid, false);
 137             } else if (option.equals(LIVE_HISTO_OPTION)) {
 138                 histo(pid, true);
 139             } else if (option.startsWith(DUMP_OPTION_PREFIX)) {
 140                 dump(pid, option);
 141             } else {
 142                 usage();
 143             }
 144         }
 145     }
 146 
 147     // Invoke SA tool  with the given arguments
 148     private static void runTool(String option, String args[]) throws Exception {
 149         String[][] tools = {
 150             { "-pmap",          "sun.jvm.hotspot.tools.PMap"             },
 151             { "-heap",          "sun.jvm.hotspot.tools.HeapSummary"      },
 152             { "-heap:format=b", "sun.jvm.hotspot.tools.HeapDumper"       },
 153             { "-histo",         "sun.jvm.hotspot.tools.ObjectHistogram"  },
 154             { "-clstats",       "sun.jvm.hotspot.tools.ClassLoaderStats" },
 155             { "-finalizerinfo", "sun.jvm.hotspot.tools.FinalizerInfo"    },
 156         };
 157 
 158         String tool = null;
 159 
 160         // -dump option needs to be handled in a special way
 161         if (option.startsWith(DUMP_OPTION_PREFIX)) {
 162             // first check that the option can be parsed
 163             String fn = parseDumpOptions(option);
 164             if (fn == null) usage();
 165 
 166             // tool for heap dumping
 167             tool = "sun.jvm.hotspot.tools.HeapDumper";
 168 
 169             // HeapDumper -f <file>
 170             args = prepend(fn, args);
 171             args = prepend("-f", args);
 172         } else {
 173             int i=0;
 174             while (i < tools.length) {
 175                 if (option.equals(tools[i][0])) {
 176                     tool = tools[i][1];
 177                     break;
 178                 }
 179                 i++;
 180             }
 181         }
 182         if (tool == null) {
 183             usage();   // no mapping to tool
 184         }
 185 
 186         // Tool not available on this  platform.
 187         Class<?> c = loadClass(tool);
 188         if (c == null) {
 189             usage();
 190         }
 191 
 192         // invoke the main method with the arguments
 193         Class[] argTypes = { String[].class } ;
 194         Method m = c.getDeclaredMethod("main", argTypes);
 195 
 196         Object[] invokeArgs = { args };
 197         m.invoke(null, invokeArgs);
 198     }
 199 
 200     // loads the given class using the system class loader
 201     private static Class<?> loadClass(String name) {
 202         //
 203         // We specify the system clas loader so as to cater for development
 204         // environments where this class is on the boot class path but sa-jdi.jar
 205         // is on the system class path. Once the JDK is deployed then both
 206         // tools.jar and sa-jdi.jar are on the system class path.
 207         //
 208         try {
 209             return Class.forName(name, true,
 210                                  ClassLoader.getSystemClassLoader());
 211         } catch (Exception x)  { }
 212         return null;
 213     }
 214 
 215     private static final String LIVE_OBJECTS_OPTION = "-live";
 216     private static final String ALL_OBJECTS_OPTION = "-all";
 217     private static void histo(String pid, boolean live) throws IOException {
 218         VirtualMachine vm = attach(pid);
 219         InputStream in = ((HotSpotVirtualMachine)vm).
 220             heapHisto(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION);
 221         drain(vm, in);
 222     }
 223 
 224     private static void dump(String pid, String options) throws IOException {
 225         // parse the options to get the dump filename
 226         String filename = parseDumpOptions(options);
 227         if (filename == null) {
 228             usage();  // invalid options or no filename
 229         }
 230 
 231         // get the canonical path - important to avoid just passing
 232         // a "heap.bin" and having the dump created in the target VM
 233         // working directory rather than the directory where jmap
 234         // is executed.
 235         filename = new File(filename).getCanonicalPath();
 236 
 237         // dump live objects only or not
 238         boolean live = isDumpLiveObjects(options);
 239 
 240         VirtualMachine vm = attach(pid);
 241         System.out.println("Dumping heap to " + filename + " ...");
 242         InputStream in = ((HotSpotVirtualMachine)vm).
 243             dumpHeap((Object)filename,
 244                      (live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION));
 245         drain(vm, in);
 246     }
 247 
 248     // Parse the options to the -dump option. Valid options are format=b and
 249     // file=<file>. Returns <file> if provided. Returns null if <file> not
 250     // provided, or invalid option.
 251     private static String parseDumpOptions(String arg) {
 252         assert arg.startsWith(DUMP_OPTION_PREFIX);
 253 
 254         String filename = null;
 255 
 256         // options are separated by comma (,)
 257         String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(",");
 258 
 259         for (int i=0; i<options.length; i++) {
 260             String option = options[i];
 261 
 262             if (option.equals("format=b")) {
 263                 // ignore format (not needed at this time)
 264             } else if (option.equals("live")) {
 265                 // a valid suboption
 266             } else {
 267 
 268                 // file=<file> - check that <file> is specified
 269                 if (option.startsWith("file=")) {
 270                     filename = option.substring(5);
 271                     if (filename.length() == 0) {
 272                         return null;
 273                     }
 274                 } else {
 275                     return null;  // option not recognized
 276                 }
 277             }
 278         }
 279         return filename;
 280     }
 281 
 282     private static boolean isDumpLiveObjects(String arg) {
 283         // options are separated by comma (,)
 284         String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(",");
 285         for (String suboption : options) {
 286             if (suboption.equals("live")) {
 287                 return true;
 288             }
 289         }
 290         return false;
 291     }
 292 
 293     // Attach to <pid>, existing if we fail to attach
 294     private static VirtualMachine attach(String pid) {
 295         try {
 296             return VirtualMachine.attach(pid);
 297         } catch (Exception x) {
 298             String msg = x.getMessage();
 299             if (msg != null) {
 300                 System.err.println(pid + ": " + msg);
 301             } else {
 302                 x.printStackTrace();
 303             }
 304             if ((x instanceof AttachNotSupportedException) && haveSA()) {
 305                 System.err.println("The -F option can be used when the " +
 306                   "target process is not responding");
 307             }
 308             System.exit(1);
 309             return null; // keep compiler happy
 310         }
 311     }
 312 
 313     // Read the stream from the target VM until EOF, then detach
 314     private static void drain(VirtualMachine vm, InputStream in) throws IOException {
 315         // read to EOF and just print output
 316         byte b[] = new byte[256];
 317         int n;
 318         do {
 319             n = in.read(b);
 320             if (n > 0) {
 321                 String s = new String(b, 0, n, "UTF-8");
 322                 System.out.print(s);
 323             }
 324         } while (n > 0);
 325         in.close();
 326         vm.detach();
 327     }
 328 
 329     // return a new string array with arg as the first element
 330     private static String[] prepend(String arg, String args[]) {
 331         String[] newargs = new String[args.length+1];
 332         newargs[0] = arg;
 333         System.arraycopy(args, 0, newargs, 1, args.length);
 334         return newargs;
 335     }
 336 
 337     // returns true if SA is available
 338     private static boolean haveSA() {
 339         Class<?> c = loadClass("sun.jvm.hotspot.tools.HeapSummary");
 340         return (c != null);
 341     }
 342 
 343     // print usage message
 344     private static void usage() {
 345         System.out.println("Usage:");
 346         if (haveSA()) {
 347             System.out.println("    jmap [option] <pid>");
 348             System.out.println("        (to connect to running process)");
 349             System.out.println("    jmap [option] <executable <core>");
 350             System.out.println("        (to connect to a core file)");
 351             System.out.println("    jmap [option] [server_id@]<remote server IP or hostname>");
 352             System.out.println("        (to connect to remote debug server)");
 353             System.out.println("");
 354             System.out.println("where <option> is one of:");
 355             System.out.println("    <none>               to print same info as Solaris pmap");
 356             System.out.println("    -heap                to print java heap summary");
 357             System.out.println("    -histo[:live]        to print histogram of java object heap; if the \"live\"");
 358             System.out.println("                         suboption is specified, only count live objects");
 359             System.out.println("    -clstats             to print class loader statistics");
 360             System.out.println("    -finalizerinfo       to print information on objects awaiting finalization");
 361             System.out.println("    -dump:<dump-options> to dump java heap in hprof binary format");
 362             System.out.println("                         dump-options:");
 363             System.out.println("                           live         dump only live objects; if not specified,");
 364             System.out.println("                                        all objects in the heap are dumped.");
 365             System.out.println("                           format=b     binary format");
 366             System.out.println("                           file=<file>  dump heap to <file>");
 367             System.out.println("                         Example: jmap -dump:live,format=b,file=heap.bin <pid>");
 368             System.out.println("    -F                   force. Use with -dump:<dump-options> <pid> or -histo");
 369             System.out.println("                         to force a heap dump or histogram when <pid> does not");
 370             System.out.println("                         respond. The \"live\" suboption is not supported");
 371             System.out.println("                         in this mode.");
 372             System.out.println("    -h | -help           to print this help message");
 373             System.out.println("    -J<flag>             to pass <flag> directly to the runtime system");
 374         } else {
 375             System.out.println("    jmap -histo <pid>");
 376             System.out.println("      (to connect to running process and print histogram of java object heap");
 377             System.out.println("    jmap -dump:<dump-options> <pid>");
 378             System.out.println("      (to connect to running process and dump java heap)");
 379             System.out.println("");
 380             System.out.println("    dump-options:");
 381             System.out.println("      format=b     binary default");
 382             System.out.println("      file=<file>  dump heap to <file>");
 383             System.out.println("");
 384             System.out.println("    Example:       jmap -dump:format=b,file=heap.bin <pid>");
 385         }
 386 
 387         System.exit(1);
 388     }
 389 }