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(1); // 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("-help") || arg.equals("-h")) {
  81                 usage(0);
  82             } else if (arg.equals(FORCE_SA_OPTION)) {
  83                 useSA = true;
  84             } else {
  85                 if (option != null) {
  86                     usage(1);  // option already specified
  87                 }
  88                 option = arg;
  89             }
  90             optionCount++;
  91         }
  92 
  93         // if no option provided then use default.
  94         if (option == null) {
  95             option = DEFAULT_OPTION;
  96         }
  97         if (option.matches(SA_TOOL_OPTIONS)) {
  98             useSA = true;
  99         }
 100 
 101         // Next we check the parameter count. For the SA tools there are
 102         // one or two parameters. For the built-in -dump option there is
 103         // only one parameter (the process-id)
 104         int paramCount = args.length - optionCount;
 105         if (paramCount == 0 || paramCount > 2) {
 106             usage(1);
 107         }
 108 
 109         if (optionCount == 0 || paramCount != 1) {
 110             useSA = true;
 111         } else {
 112             // the parameter for the -dump option is a process-id.
 113             // If it doesn't parse to a number then it must be SA
 114             // debug server
 115             if (!args[optionCount].matches("[0-9]+")) {
 116                 useSA = true;
 117             }
 118         }
 119 
 120 
 121         // at this point we know if we are executing an SA tool or a built-in
 122         // option.
 123 
 124         if (useSA) {
 125             // parameters (<pid> or <exe> <core>)
 126             String params[] = new String[paramCount];
 127             for (int i=optionCount; i<args.length; i++ ){
 128                 params[i-optionCount] = args[i];
 129             }
 130             runTool(option, params);
 131 
 132         } else {
 133             String pid = args[1];
 134             // Here we handle the built-in options
 135             // As more options are added we should create an abstract tool class and
 136             // have a table to map the options
 137             if (option.equals(HISTO_OPTION)) {
 138                 histo(pid, false);
 139             } else if (option.equals(LIVE_HISTO_OPTION)) {
 140                 histo(pid, true);
 141             } else if (option.startsWith(DUMP_OPTION_PREFIX)) {
 142                 dump(pid, option);
 143             } else {
 144                 usage(1);
 145             }
 146         }
 147     }
 148 
 149     // Invoke SA tool  with the given arguments
 150     private static void runTool(String option, String args[]) throws Exception {
 151         String[][] tools = {
 152             { "-pmap",          "sun.jvm.hotspot.tools.PMap"             },
 153             { "-heap",          "sun.jvm.hotspot.tools.HeapSummary"      },
 154             { "-heap:format=b", "sun.jvm.hotspot.tools.HeapDumper"       },
 155             { "-histo",         "sun.jvm.hotspot.tools.ObjectHistogram"  },
 156             { "-clstats",       "sun.jvm.hotspot.tools.ClassLoaderStats" },
 157             { "-finalizerinfo", "sun.jvm.hotspot.tools.FinalizerInfo"    },
 158         };
 159 
 160         String tool = null;
 161 
 162         // -dump option needs to be handled in a special way
 163         if (option.startsWith(DUMP_OPTION_PREFIX)) {
 164             // first check that the option can be parsed
 165             String fn = parseDumpOptions(option);
 166             if (fn == null) {
 167                 usage(1);
 168             }
 169 
 170             // tool for heap dumping
 171             tool = "sun.jvm.hotspot.tools.HeapDumper";
 172 
 173             // HeapDumper -f <file>
 174             args = prepend(fn, args);
 175             args = prepend("-f", args);
 176         } else {
 177             int i=0;
 178             while (i < tools.length) {
 179                 if (option.equals(tools[i][0])) {
 180                     tool = tools[i][1];
 181                     break;
 182                 }
 183                 i++;
 184             }
 185         }
 186         if (tool == null) {
 187             usage(1);   // no mapping to tool
 188         }
 189 
 190         // Tool not available on this  platform.
 191         Class<?> c = loadClass(tool);
 192         if (c == null) {
 193             usage(1);
 194         }
 195 
 196         // invoke the main method with the arguments
 197         Class[] argTypes = { String[].class } ;
 198         Method m = c.getDeclaredMethod("main", argTypes);
 199 
 200         Object[] invokeArgs = { args };
 201         m.invoke(null, invokeArgs);
 202     }
 203 
 204     // loads the given class using the system class loader
 205     private static Class<?> loadClass(String name) {
 206         //
 207         // We specify the system clas loader so as to cater for development
 208         // environments where this class is on the boot class path but sa-jdi.jar
 209         // is on the system class path. Once the JDK is deployed then both
 210         // tools.jar and sa-jdi.jar are on the system class path.
 211         //
 212         try {
 213             return Class.forName(name, true,
 214                                  ClassLoader.getSystemClassLoader());
 215         } catch (Exception x)  { }
 216         return null;
 217     }
 218 
 219     private static final String LIVE_OBJECTS_OPTION = "-live";
 220     private static final String ALL_OBJECTS_OPTION = "-all";
 221     private static void histo(String pid, boolean live) throws IOException {
 222         VirtualMachine vm = attach(pid);
 223         InputStream in = ((HotSpotVirtualMachine)vm).
 224             heapHisto(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION);
 225         drain(vm, in);
 226     }
 227 
 228     private static void dump(String pid, String options) throws IOException {
 229         // parse the options to get the dump filename
 230         String filename = parseDumpOptions(options);
 231         if (filename == null) {
 232             usage(1);  // 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).
 247             dumpHeap((Object)filename,
 248                      (live ? LIVE_OBJECTS_OPTION : 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()).split(",");
 262 
 263         for (int i=0; i<options.length; i++) {
 264             String option = options[i];
 265 
 266             if (option.equals("format=b")) {
 267                 // ignore format (not needed at this time)
 268             } else if (option.equals("live")) {
 269                 // a valid suboption
 270             } else {
 271 
 272                 // file=<file> - check that <file> is specified
 273                 if (option.startsWith("file=")) {
 274                     filename = option.substring(5);
 275                     if (filename.length() == 0) {
 276                         return null;
 277                     }
 278                 } else {
 279                     return null;  // option not recognized
 280                 }
 281             }
 282         }
 283         return filename;
 284     }
 285 
 286     private static boolean isDumpLiveObjects(String arg) {
 287         // options are separated by comma (,)
 288         String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(",");
 289         for (String suboption : options) {
 290             if (suboption.equals("live")) {
 291                 return true;
 292             }
 293         }
 294         return false;
 295     }
 296 
 297     // Attach to <pid>, existing if we fail to attach
 298     private static VirtualMachine attach(String pid) {
 299         try {
 300             return VirtualMachine.attach(pid);
 301         } catch (Exception x) {
 302             String msg = x.getMessage();
 303             if (msg != null) {
 304                 System.err.println(pid + ": " + msg);
 305             } else {
 306                 x.printStackTrace();
 307             }
 308             if ((x instanceof AttachNotSupportedException) && haveSA()) {
 309                 System.err.println("The -F option can be used when the " +
 310                   "target process is not responding");
 311             }
 312             System.exit(1);
 313             return null; // keep compiler happy
 314         }
 315     }
 316 
 317     // Read the stream from the target VM until EOF, then detach
 318     private static void drain(VirtualMachine vm, InputStream in) throws IOException {
 319         // read to EOF and just print output
 320         byte b[] = new byte[256];
 321         int n;
 322         do {
 323             n = in.read(b);
 324             if (n > 0) {
 325                 String s = new String(b, 0, n, "UTF-8");
 326                 System.out.print(s);
 327             }
 328         } while (n > 0);
 329         in.close();
 330         vm.detach();
 331     }
 332 
 333     // return a new string array with arg as the first element
 334     private static String[] prepend(String arg, String args[]) {
 335         String[] newargs = new String[args.length+1];
 336         newargs[0] = arg;
 337         System.arraycopy(args, 0, newargs, 1, args.length);
 338         return newargs;
 339     }
 340 
 341     // returns true if SA is available
 342     private static boolean haveSA() {
 343         Class<?> c = loadClass("sun.jvm.hotspot.tools.HeapSummary");
 344         return (c != null);
 345     }
 346 
 347     // print usage message
 348     private static void usage(int exit) {
 349         System.err.println("Usage:");
 350         if (haveSA()) {
 351             System.err.println("    jmap [option] <pid>");
 352             System.err.println("        (to connect to running process)");
 353             System.err.println("    jmap [option] <executable <core>");
 354             System.err.println("        (to connect to a core file)");
 355             System.err.println("    jmap [option] [server_id@]<remote server IP or hostname>");
 356             System.err.println("        (to connect to remote debug server)");
 357             System.err.println("");
 358             System.err.println("where <option> is one of:");
 359             System.err.println("    <none>               to print same info as Solaris pmap");
 360             System.err.println("    -heap                to print java heap summary");
 361             System.err.println("    -histo[:live]        to print histogram of java object heap; if the \"live\"");
 362             System.err.println("                         suboption is specified, only count live objects");
 363             System.err.println("    -clstats             to print class loader statistics");
 364             System.err.println("    -finalizerinfo       to print information on objects awaiting finalization");
 365             System.err.println("    -dump:<dump-options> to dump java heap in hprof binary format");
 366             System.err.println("                         dump-options:");
 367             System.err.println("                           live         dump only live objects; if not specified,");
 368             System.err.println("                                        all objects in the heap are dumped.");
 369             System.err.println("                           format=b     binary format");
 370             System.err.println("                           file=<file>  dump heap to <file>");
 371             System.err.println("                         Example: jmap -dump:live,format=b,file=heap.bin <pid>");
 372             System.err.println("    -F                   force. Use with -dump:<dump-options> <pid> or -histo");
 373             System.err.println("                         to force a heap dump or histogram when <pid> does not");
 374             System.err.println("                         respond. The \"live\" suboption is not supported");
 375             System.err.println("                         in this mode.");
 376             System.err.println("    -h | -help           to print this help message");
 377             System.err.println("    -J<flag>             to pass <flag> directly to the runtime system");
 378         } else {
 379             System.err.println("    jmap -histo <pid>");
 380             System.err.println("      (to connect to running process and print histogram of java object heap");
 381             System.err.println("    jmap -dump:<dump-options> <pid>");
 382             System.err.println("      (to connect to running process and dump java heap)");
 383             System.err.println("");
 384             System.err.println("    dump-options:");
 385             System.err.println("      format=b     binary default");
 386             System.err.println("      file=<file>  dump heap to <file>");
 387             System.err.println("");
 388             System.err.println("    Example:       jmap -dump:format=b,file=heap.bin <pid>");
 389         }
 390 
 391         System.exit(exit);
 392     }
 393 }