This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [Patch] Java: Add heap dump and analyze support.


New version of the patch.

Tom Tromey wrote:
"David" == David Daney <ddaney@avtrex.com> writes:

I wonder if we could fix this by putting the new tool code into
standard.omit, and then compile it separately using a special rule of
some kind, with a classpath pointing to the tools zip.

David> Ok, I added some rules to classpath/lib/Makefile.am, that parallel the David> special libgcj rules, to build gcj tools with a different classpath. David> To build more packages this way (i.e. gcj_dbtool), it should be as David> simple as adding the package name to the list in libjava's David> configure.ac. There will no longer be any excuses to prevent us from David> converting gcj_dbtool to use the getopt things.

David> OK to commit if no regressions?

I was hoping for 2 things, which I didn't communicate well.

One is, no changes to libjava/classpath/.  But I realize now that this
is too hard.

The other is, not adding the tools classes to AM_GCJFLAGS.

Instead of adding a new script in classpath/lib, would it be posssible
to build the new tool code in classpath/tools/Makefile? Then the new
tool could be built in libjava/Makefile.am following the recipe for
all the other classpath tools.
Done (I hope!).

That should be the last problem with the patch.

Initial build looks good. I will finish testing tonight.

OK to commit if no regressions?

gcc/java:
2007-02-14  David Daney  <ddaney@avtrex.com>

   * Make-lang.in (JAVA_MANFILES): Add doc/gc-analyze.1.
   (java.maintainer-clean):Add gc-analyze.1.
   (.INTERMEDIATE): Add gc-analyze.pod.
   (gc-analyze.pod): New rule.
   (java.install-man): Install gc-analyze.1
   * gcj.texi: Add new section for the gc-analyze program.

libjava:
2007-02-14  Johannes Schmidt  <jschmidt@avtrex.com>
   David Daney  <ddaney@avtrex.com>

* configure.ac: Create vm-tools-packages file. Add
gnu/gcj/tools/gc_analyze to standard.omit and vm-tools-packages.
Check for /proc/self/maps.
* Makefile.am (bin_PROGRAMS): Added gc-analyze.
(gc_analyze_SOURCES): New.
(gc_analyze_LDFLAGS): New.
(gc_analyze_LINK): New.
(gc_analyze_LDADD): New.
(gc_analyze_DEPENDENCIES): New.
(nat_source_files): Add gnu/gcj/util/natGCInfo.cc.
* Makefile.in: Regenerated.
* configure: Regenerated.
* include/config.h.in: Regenerated.
* sources.am: Regenerated.
* scripts/makemake.tcl: Don't include gc-analyze classes in libgcj.
* gnu/gcj/tools/gc_analyze/SymbolLookup.java: New.
* gnu/gcj/tools/gc_analyze/ObjectMap.java: New.
* gnu/gcj/tools/gc_analyze/MemoryMap.java: New.
* gnu/gcj/tools/gc_analyze/SymbolTable.java: New.
* gnu/gcj/tools/gc_analyze/BlockMap.java: New.
* gnu/gcj/tools/gc_analyze/BytePtr.java: New.
* gnu/gcj/tools/gc_analyze/ItemList.java: New.
* gnu/gcj/tools/gc_analyze/ToolPrefix.java: New.
* gnu/gcj/tools/gc_analyze/MemoryAnalyze.java: New.
* gnu/gcj/util/GCInfo.java: New.
* gnu/gcj/util/GCInfo.h: New.
* gnu/gcj/util/natGCInfo.cc: New.
* gnu/gcj/util/UtilPermission.java: New.
* gnu/gcj/util/UtilPermission.h: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/SymbolTable.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/ObjectMap$ObjectItem.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryMap$RangeComparator.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/BlockMap$PtrMarks.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryMap$Range.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/BlockMap.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/BytePtr.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$SubstringComparator.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/ItemList.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/ToolPrefix.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$1$Info.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$1.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$2.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$3.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$4.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryAnalyze$OptionParser.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/BlockMap$SizeKind.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/SymbolLookup.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/ObjectMap.class: New.
* classpath/tools/gnu/gcj/tools/gc_analyze/MemoryMap.class: New.
* classpath/lib/gnu/gcj/util/GCInfo.class: New.
* classpath/lib/gnu/gcj/util/UtilPermission.class: New.


libjava/classpath:
2007-02-14  David Daney  <ddaney@avtrex.com>

   * tools/Makefile.am (TOOLS_ZIP): Add classes from vm-tools-packages.
   * tools/Makefile.in: Regenerated.


Index: gcc/java/Make-lang.in
===================================================================
--- gcc/java/Make-lang.in	(revision 121878)
+++ gcc/java/Make-lang.in	(working copy)
@@ -139,7 +139,7 @@ java.pdf: doc/gcj.pdf
 java.html: $(build_htmldir)/java/index.html
 JAVA_MANFILES = doc/gcj.1 doc/jcf-dump.1 doc/gij.1 \
                 doc/jv-convert.1 doc/grmic.1 doc/grmiregistry.1 \
-		doc/gcj-dbtool.1
+		doc/gcj-dbtool.1 doc/gc-analyze.1
 
 java.man: $(JAVA_MANFILES)
 
@@ -208,6 +208,7 @@ java.maintainer-clean:
 	-rm -f $(docobjdir)/grmic.1
 	-rm -f $(docobjdir)/grmiregistry.1
 	-rm -f $(docobjdir)/gcj-dbtool.1
+	-rm -f $(docobjdir)/gc-analyze.1
 #
 # Stage hooks:
 # The main makefile has already created stage?/java.
@@ -318,7 +319,7 @@ $(build_htmldir)/java/index.html: $(TEXI
 	$(TEXI2HTML) -I $(gcc_docdir)/include -I $(srcdir)/java -o $(@D) $<
 
 .INTERMEDIATE: gcj.pod jcf-dump.pod gij.pod \
-  jv-convert.pod grmic.pod grmiregistry.pod gcj-dbtool.pod
+  jv-convert.pod grmic.pod grmiregistry.pod gcj-dbtool.pod gc-analyze.pod
 
 gcj.pod: java/gcj.texi
 	-$(TEXI2POD) -D gcj < $< > $@
@@ -334,15 +335,17 @@ grmiregistry.pod: java/gcj.texi
 	-$(TEXI2POD) -D grmiregistry < $< > $@
 gcj-dbtool.pod: java/gcj.texi
 	-$(TEXI2POD) -D gcj-dbtool < $< > $@
+gc-analyze.pod: java/gcj.texi
+	-$(TEXI2POD) -D gc-analyze < $< > $@
 
 # Install the man pages.
 java.install-man: installdirs \
                   $(DESTDIR)$(man1dir)/$(JAVA_INSTALL_NAME)$(man1ext) \
 		  $(JAVA_TARGET_INDEPENDENT_BIN_TOOLS:%=doc/%.1) \
 		  doc/gij.1 doc/jv-convert.1 doc/grmic.1 doc/grmiregistry.1 \
-		  doc/gcj-dbtool.1
+		  doc/gcj-dbtool.1 doc/gc-analyze.1
 	for tool in $(JAVA_TARGET_INDEPENDENT_BIN_TOOLS) \
-		gij jv-convert grmic grmiregistry gcj-dbtool ; do \
+		gij jv-convert grmic grmiregistry gcj-dbtool gc-analyze ; do \
 	  tool_transformed_name=`echo $$tool|sed '$(program_transform_name)'`; \
 	  man_name=$(DESTDIR)$(man1dir)/$${tool_transformed_name}$(man1ext); \
 	  rm -f $$man_name ; \
Index: gcc/java/gcj.texi
===================================================================
--- gcc/java/gcj.texi	(revision 121878)
+++ gcc/java/gcj.texi	(working copy)
@@ -17,7 +17,7 @@
 @c the word ``Java'.
 
 @c When this manual is copyrighted.
-@set copyrights-gcj 2001, 2002, 2003, 2004, 2005
+@set copyrights-gcj 2001, 2002, 2003, 2004, 2005, 2006, 2007
 
 @c Versions
 @set which-gcj GCC-@value{version-GCC}
@@ -79,6 +79,8 @@ man page gfdl(7).
                             Generate stubs for Remote Method Invocation.
 * grmiregistry: (gcj)Invoking grmiregistry.
                             The remote object registry.
+* gc-analyze: (gcj)Invoking gc-analyze.
+                            Analyze Garbage Collector (GC) memory dumps.
 @end direntry
 @end format
 
@@ -125,6 +127,7 @@ files and object files, and it can read 
 * Invoking jv-convert:: Converting from one encoding to another
 * Invoking grmic::      Generate stubs for Remote Method Invocation.
 * Invoking grmiregistry:: The remote object registry.
+* Invoking gc-analyze:: Analyze Garbage Collector (GC) memory dumps.
 * About CNI::           Description of the Compiled Native Interface
 * System properties::   Modifying runtime behavior of the libgcj library
 * Resources::		Where to look for more information
@@ -1412,6 +1415,89 @@ Print version information, then exit.
 @c man end
 
 
+@node Invoking gc-analyze
+@chapter Invoking gc-analyze
+
+@c man title gc-analyze Analyze Garbage Collector (GC) memory dumps
+
+@c man begin SYNOPSIS gc-analyze
+@command{gc-analyze} [@option{OPTION}] @dots{} [@var{file}]
+@ignore
+  [@option{-v}]
+  [@option{--verbose}]
+  [@option{-p} @var{tool-prefix}]
+  [@option{-d} @var{directory}]
+  [@option{--version}]
+  [@option{--help}]
+@end ignore
+@c man end
+
+@c man begin DESCRIPTION gc-analyze
+
+@command{gc-analyze} prints an analysis of a GC memory dump to
+standard out.
+
+The memory dumps may be created by calling
+@code{gnu.gcj.util.GCInfo.enumerate(String namePrefix)} from java
+code.  A memory dump will be created on an out of memory condition if
+@code{gnu.gcj.util.GCInfo.setOOMDump(String namePrefix)} is called
+before the out of memory occurs.
+
+Running this program will create two files: @file{TestDump001} and
+@file{TestDump001.bytes}.
+
+@example
+import gnu.gcj.util.*;
+import java.util.*;
+
+public class GCDumpTest
+@{
+    static public void main(String args[])
+    @{
+        ArrayList<String> l = new ArrayList<String>(1000);
+        
+        for (int i = 1; i < 1500; i++) @{
+            l.add("This is string #" + i);
+        @}
+        GCInfo.enumerate("TestDump");
+    @}
+@}
+@end example
+
+The memory dump may then be displayed by running:
+
+@example
+gc-analyze -v TestDump001
+@end example
+
+@c FIXME: Add real information here.
+@c This really isn't much more than the --help output.
+
+@c man end
+
+@c man begin OPTIONS gc-analyze
+
+@table @gcctabopt
+@item --verbose
+@itemx -v
+Verbose output.
+
+@item -p @var{tool-prefix}
+Prefix added to the names of the @command{nm} and @command{readelf} commands.
+
+@item -d @var{directory}
+Directory that contains the executable and shared libraries used when
+the dump was generated.
+
+@item --help
+Print a help message, then exit.
+
+@item --version
+Print version information, then exit.
+@end table
+
+@c man end
+
 @node About CNI
 @chapter About CNI
 
Index: libjava/scripts/makemake.tcl
===================================================================
--- libjava/scripts/makemake.tcl	(revision 121878)
+++ libjava/scripts/makemake.tcl	(working copy)
@@ -42,6 +42,7 @@ set package_map(.) package
 # These are ignored in Classpath.
 set package_map(gnu/test) ignore
 set package_map(gnu/javax/swing/plaf/gtk) ignore
+set package_map(gnu/gcj/tools/gc_analyze) ignore
 
 set package_map(gnu/java/awt/peer/swing) bc
 
Index: libjava/configure.ac
===================================================================
--- libjava/configure.ac	(revision 121878)
+++ libjava/configure.ac	(working copy)
@@ -295,6 +295,12 @@ if test "$use_x_awt" != yes; then
    echo gnu/java/awt/peer/x >> standard.omit
 fi
 
+# Tools that need to be compiled against classpath's tools classes
+for package in gnu/gcj/tools/gc_analyze ; do
+    echo $package >> standard.omit
+    echo $package >> vm-tools-packages
+done
+
 if test -z "${with_multisubdir}"; then
    builddotdot=.
 else
@@ -1012,10 +1018,14 @@ else
    if test x"$cross_compiling" = x"no"; then
      AC_CHECK_FILES(/proc/self/exe, [
        AC_DEFINE(HAVE_PROC_SELF_EXE, 1, [Define if you have /proc/self/exe])])
+     AC_CHECK_FILES(/proc/self/maps, [
+       AC_DEFINE(HAVE_PROC_SELF_MAPS, 1,
+         [Define if you have /proc/self/maps])])
    else
      case $host in
      *-linux*)
        AC_DEFINE(HAVE_PROC_SELF_EXE, 1, [Define if you have /proc/self/exe])
+       AC_DEFINE(HAVE_PROC_SELF_MAPS, 1, [Define if you have /proc/self/maps])
        ;;
      esac
    fi
Index: libjava/classpath/tools/Makefile.am
===================================================================
--- libjava/classpath/tools/Makefile.am	(revision 121955)
+++ libjava/classpath/tools/Makefile.am	(working copy)
@@ -155,12 +155,19 @@ $(TOOLS_ZIP): $(TOOLS_JAVA_FILES)
 ##	mkdir classes asm
 	find $(srcdir)/external/asm -name '*.java' -print > asm.lst
 	find $(srcdir)/gnu/classpath/tools -name '*.java' -print > classes.lst
-	cat classes.lst asm.lst > all-classes.lst
+	if [ -f $(top_builddir)/../vm-tools-packages ]; then \
+	    for pkg in `cat $(top_builddir)/../vm-tools-packages`; do \
+	      $(FIND) $(top_srcdir)/../$$pkg -follow -name '*.java' -print >> vm-tools.lst; \
+	    done \
+	else \
+	    echo -n > vm-tools.lst; \
+	fi
+	cat classes.lst asm.lst vm-tools.lst > all-classes.lst
 if JAVA_MAINTAINER_MODE
 ## Compile ASM separately as it is latin-1 encoded.
 	AC=`echo $(JCOMPILER) | sed -e 's/UTF-8/ISO-8859-1/g'`; \
 	  $$AC -g -w -d $(srcdir)/asm @asm.lst
-	$(JCOMPILER) -g -w -d $(srcdir)/classes @classes.lst
+	$(JCOMPILER) -g -w -d $(srcdir)/classes @classes.lst @vm-tools.lst
 ## Copy over tools resource files.
 	@list=`cd $(top_srcdir)/resource && find gnu/classpath/tools -name \*.properties -print`; \
 	for p in $$list; do \
Index: libjava/gnu/gcj/tools/gc_analyze/SymbolLookup.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/SymbolLookup.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/SymbolLookup.java	(revision 0)
@@ -0,0 +1,112 @@
+/* SymbolLookup.java -- Finds class names by analyzing memory.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+class SymbolLookup
+{
+  MemoryMap memoryMap;
+
+  public SymbolLookup(BufferedReader reader,
+                      String rawFileName)
+    throws IOException
+  {
+    memoryMap = new MemoryMap(reader, rawFileName);
+  }
+
+  public String decodeUTF8(long address) throws IOException
+  {
+    return decodeUTF8(address, -1);
+  }
+  
+  public String decodeUTF8(long address, int limit) throws IOException
+  {
+    if (address == 0)
+      return null;
+
+    BytePtr utf8 = memoryMap.getBytePtr(address, 64);
+
+    if (utf8 == null)
+      return null;
+
+    int len = utf8.getShort(1);
+    int hash16 = utf8.getShort(0) & 0xffff;
+
+    if (len <= 0 || (limit > 0 && len > (limit - 4)))
+      return null;
+    
+    if (len > utf8.getsize() + 4)
+      utf8 = memoryMap.getBytePtr(address, len + 4);
+
+    if (utf8 == null)
+      return null;
+    
+    StringBuilder sb = new StringBuilder(len);
+    int pos = 4;
+    len += 4;
+    
+    while (pos < len)
+      {
+        int f = utf8.getByte(pos++);
+        if ((f & 0x80) == 0)
+          {
+            sb.append((char)f);
+          }
+        else if ((f & 0xe0) == 0xc0)
+          {
+            int s = utf8.getByte(pos++);
+            char c = (char)(((f & 0x1f) << 6) | (s & 0x80));
+            sb.append(c);
+          }
+        else if ((f & 0xe0) == 0xe0)
+          {
+            int s = utf8.getByte(pos++);
+            int t = utf8.getByte(pos++);
+            char c = (char)(((f & 0x0f) << 12)
+                            | ((s & 0x80) << 6) | (t & 0x80));
+            sb.append(c);
+          }
+        else 
+          break;  // Bad utf8
+      }
+    String rv = sb.toString();
+    if (hash16 == (rv.hashCode() & 0xffff))
+      return rv;
+    else
+      return null;
+  }
+
+  public String getSymbolViaVtable(long address) throws IOException
+  {
+    return memoryMap.getSymbol(address);
+  }
+
+  public String getSymbol(long address) throws IOException
+  {
+    String symbol = memoryMap.getSymbol(address);
+    if (null != symbol)
+      return symbol;
+    
+    BytePtr klass = memoryMap.getBytePtr(address, 3 * memoryMap.wordSize);
+    if (klass == null)
+      return null;
+    
+    long nameUTF8p = klass.getWord(2);
+    
+    return decodeUTF8(nameUTF8p);
+  }
+
+  BytePtr getBytePtr(long addr, int length) throws IOException
+  {
+    return memoryMap.getBytePtr(addr, length);
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/ObjectMap.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/ObjectMap.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/ObjectMap.java	(revision 0)
@@ -0,0 +1,140 @@
+/* ObjectMap.java -- Contains a map of all objects keyed by their addresses.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+class ObjectMap implements Iterable<Map.Entry<Long, ObjectMap.ObjectItem>>
+{
+
+  class ObjectItem
+  {
+    int used;
+    int size;
+    int kind;
+    long klass;
+    long data;
+    long ptr;
+    String typeName;
+    String string; // only for string objects
+    boolean stringData; // character array pointed to by a string
+    ObjectItem reference; // object at reference points to this
+
+    ItemList points_to = new ItemList();
+    ItemList pointed_by = new ItemList();
+  }
+
+  private TreeMap<Long, ObjectItem> map = new TreeMap<Long, ObjectItem>();
+
+  public Iterator<Map.Entry<Long, ObjectItem>> iterator()
+  {
+    return map.entrySet().iterator();
+  }
+
+  public ObjectItem get(long ptr)
+  {
+    ObjectItem item = map.get(ptr);
+    return item;
+  }
+
+  public ObjectMap(BufferedReader reader) throws IOException
+  {
+    outer_loop:
+    for (;;)
+      {
+        String s = reader.readLine();
+        if (s == null)
+          break;
+        if (s.indexOf("Begin object map") >= 0)
+          {
+            for (;;)
+              {
+                s = reader.readLine();
+                if (s.indexOf("End object map") >= 0)
+                  break outer_loop;
+                String[] items = s.split(",");
+                ObjectItem item = new ObjectItem();
+                long ptr = 0;
+                for (int i=0; i<items.length; i++)
+                  {
+                    String[] x = items[i].split(" ");
+                    String last = x[x.length-1];
+                    switch (i)
+                      {
+                      case 0:
+                        item.used = Integer.parseInt(last);
+                        break;
+                      case 1:
+                        ptr = MemoryMap.parseHexLong(last.substring(2));
+                        break;
+                      case 2:
+                        item.size = Integer.parseInt(last);
+                        break;
+                      case 3:
+                        item.kind = Integer.parseInt(last);
+                        break;
+                      case 4:
+                        if (last.length() > 1)
+                          item.klass =
+                            MemoryMap.parseHexLong(last.substring(2));
+                        else
+                          item.klass  = Integer.parseInt(last,16);
+                        break;
+                      case 5:
+                        try
+                          {
+                            item.data =
+                              Integer.parseInt(last.substring(2), 16);
+                          }
+                        catch (Exception e)
+                          {
+                            item.data = 0;
+                          }
+                        break;
+                      }
+                  }
+                item.ptr = ptr;
+                map.put(ptr, item);
+              } // inner loop
+          } // started inner loop
+      } // outer loop - finding begin
+    for (Map.Entry<Long, ObjectItem> me : this)
+      {
+        ObjectItem item = me.getValue();
+        if (item.data != 0)
+          {
+            // see if data is a pointer to a block
+            ObjectItem referenced = map.get(item.data);
+            if (referenced != null)
+              {
+                referenced.reference = item;
+              }
+          }
+      }
+  } // memoryMap
+
+  public void dump()
+  {
+    for (Map.Entry<Long, ObjectItem> me : this)
+      {
+        long ptr = me.getKey();
+        ObjectItem item = me.getValue();
+        System.out.println("ptr = " + Long.toHexString(ptr)
+                           + ", size = " + item.size
+                           + ", klass = " + Long.toHexString(item.klass)
+                           + ", kind = " + item.kind
+                           + ", data = " + item.data);
+      }
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/MemoryMap.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/MemoryMap.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/MemoryMap.java	(revision 0)
@@ -0,0 +1,359 @@
+/* MemoryMap.java -- Maps address ranges to their data.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Reads /proc/self/maps output from dump file.
+ * Creates map of <filename> to Range.
+ *
+ * Returns filename given address.
+ * Returns offset given address.
+ * Returns BytePtr given address.
+ *
+ */
+class MemoryMap
+{
+  static class RangeComparator implements Comparator<Range>
+  {
+    public int compare(Range r1, Range r2)
+    {
+      if (r2.end == 0 && r1.end != 0)
+        return -compare(r2, r1);
+      
+      if (r1.begin < r2.begin)
+        return -1;
+      else if (r1.begin >= r2.end)
+        return 1;
+      else
+        return 0;
+    }
+  }
+  
+  static class Range
+  {
+    long begin;
+    long end;
+
+    long offset;
+    String filename;
+    Range()
+    {
+    }
+    
+    Range(long b, long e, String s, long o)
+    {
+      begin = b;
+      end = e;
+      filename = s;
+      offset = o;
+    }
+  }
+
+  /**
+   * Parse the string as an unsigned hexadecimal number.  This is
+   * similar to Long.parseInt(s,16), but without the restriction that
+   * values that have the sign bit set not being allowed.
+   *
+   * @param s the number as a String.
+   * @return the number.
+   */
+  static long parseHexLong(String s)
+  {
+    if (s.length() > 16)
+      throw new NumberFormatException();
+    long r = 0;
+    for (int i = 0; i < s.length(); i++)
+      {
+        int digit = 0;
+        char c = s.charAt(i);
+        switch (c)
+          {
+          case '0':
+          case '1':
+          case '2':
+          case '3':
+          case '4':
+          case '5':
+          case '6':
+          case '7':
+          case '8':
+          case '9':
+            digit = c - '0';
+            break;
+          case 'a':
+          case 'b':
+          case 'c':
+          case 'd':
+          case 'e':
+          case 'f':
+            digit = 10 + c - 'a';
+            break;
+          case 'A':
+          case 'B':
+          case 'C':
+          case 'D':
+          case 'E':
+          case 'F':
+            digit = 10 + c - 'A';
+            break;
+          default:
+            throw new NumberFormatException();
+          }
+        r = (r << 4) + digit;
+      }
+    return r;
+  }
+  
+  // String filename -> Range
+  TreeSet<Range> map = new TreeSet<Range>(new RangeComparator());
+  HashMap<String, SymbolTable> symbolTables =
+    new HashMap<String, SymbolTable>();
+  ByteOrder byteOrder;
+  int wordSize;
+
+  public MemoryMap(BufferedReader reader,
+                   String rawFileName) throws IOException
+  {
+    FileChannel raw = (new RandomAccessFile(rawFileName, "r")).getChannel();
+    ByteBuffer buf = ByteBuffer.allocate(8);
+    raw.read(buf);
+    if (buf.hasRemaining())
+      {
+        raw.close();
+        throw new EOFException();
+      }
+    buf.flip();
+    wordSize = buf.get();
+    
+    if (wordSize == 8 || wordSize == 4)
+      byteOrder = ByteOrder.LITTLE_ENDIAN;
+    else
+      {
+        byteOrder = ByteOrder.BIG_ENDIAN;
+        buf.rewind();
+        wordSize = buf.getInt();
+        if (0 == wordSize)
+          wordSize = buf.getInt();
+      }
+    switch (wordSize)
+      {
+      case 4:
+      case 8:
+        break;
+      default:
+        throw new IOException("Bad .bytes file header");
+      }
+    buf = ByteBuffer.allocate(3 * wordSize);
+    buf.order(byteOrder);
+    raw.position(0L);
+
+    for(;;)
+      {
+        // Read the block header.
+        buf.clear();
+        if (-1 == raw.read(buf))
+          {
+            //EOF
+            raw.close();
+            break;
+          }
+        if (buf.hasRemaining())
+          {
+            raw.close();
+            throw new EOFException();
+          }
+        buf.flip();
+        long dummy
+          = (wordSize == 4) ? (buf.getInt() & 0xffffffffL) : buf.getLong();
+        if (dummy != wordSize)
+          throw new IOException("Bad .bytes file header");
+        long start
+          = wordSize == 4 ? (buf.getInt() & 0xffffffffL) : buf.getLong();
+        long length
+          = wordSize == 4 ? (buf.getInt() & 0xffffffffL) : buf.getLong();
+        if (length < 0L)
+          throw new IOException("Bad .bytes file header");
+      
+        long currentPos = raw.position();
+        raw.position(currentPos + length);
+    
+        Range range = new Range(start, start + length,
+                                rawFileName, currentPos);
+        map.add(range);
+      }
+
+    for (;;)
+      {
+        String s = reader.readLine();
+        if (s == null)
+          break;
+        if (s.indexOf("Begin address map") >= 0)
+          {
+            for (;;)
+              {
+                s = reader.readLine();
+                if (s.indexOf("End address map") >= 0)
+                  {
+                    dump();
+                    return;
+                  }
+                int endOfAddress = s.indexOf('-');
+                long address = parseHexLong(s.substring(0, endOfAddress));
+                int endOfAddress2 = s.indexOf(' ', endOfAddress + 1);
+                long address2 = parseHexLong(s.substring(endOfAddress + 1,
+                                                         endOfAddress2));
+                int endOfOffset = s.indexOf(' ', endOfAddress2 + 6);
+                long offset;
+                try
+                  {
+                    offset = parseHexLong(s.substring(endOfAddress2 + 6,
+                                                      endOfOffset));
+                  }
+                catch (Exception e)
+                  {
+                    offset = 0;
+                  }
+                int end = s.indexOf('/');
+
+                if (end > 0)
+                  {
+                    String file = s.substring(end);
+                    if (file.startsWith("/dev/"))
+                      continue;
+
+                    Range r = new Range(address, address2, file, offset);
+                    if (offset == 0)
+                      {
+                        // Read the file's symbol table
+                        try
+                          {
+                            File f = ToolPrefix.fileForName(file);
+                            if (f != null)
+                              {
+                                SymbolTable st = new SymbolTable(f.getPath());
+                                if (st.loadAddr != address)
+                                  st.relocation = address - st.loadAddr;
+                                symbolTables.put(file, st);
+                              }
+                          }
+                        catch (Exception ex)
+                          {
+                            ex.printStackTrace();
+                          }
+                      }
+                    map.add(r);
+                  }
+              } // inner loop
+          } // started inner loop
+      } // outer loop - finding begin
+  } // memoryMap
+
+  
+  public void dump()
+  {
+    System.out.println("MemoryMap:");
+    for (Range r : map)
+      {
+        System.out.println(Long.toHexString(r.begin) + "-"
+                           + Long.toHexString(r.end) + " -> "
+                           + r.filename + " offset "
+                           + Long.toHexString(r.offset));
+      }
+  }
+
+  Range getRange(long addr)
+  {
+    Range r = new Range();
+    r.begin = addr;
+    SortedSet<Range> t = map.tailSet(r);
+    if (t.isEmpty())
+      return null;
+    Range c = t.first();
+    if (c.begin <= addr && addr < c.end)
+      return c;
+    return null;
+  }
+  
+  String getFile(long addr)
+  {
+    Range r = getRange(addr);
+    if (null != r)
+      return r.filename;
+    return null;
+  }
+
+  long getOffset(long addr)
+  {
+    Range r = getRange(addr);
+    if (null != r)
+      return r.offset;
+    return 0L;
+  }
+
+  /**
+   * @return BytePtr which includes given address.
+   */
+  BytePtr getBytePtr(long addr, int length) throws IOException
+  {
+    Range r = getRange(addr);
+    
+    if (null == r)
+      return null;
+
+    File f = ToolPrefix.fileForName(r.filename);
+    if (null == f)
+      return null;
+    
+    if (addr + length > r.end)
+      length = (int)(r.end - addr);
+    
+    ByteBuffer b = ByteBuffer.allocate(length);
+    b.order(byteOrder);
+    
+    FileChannel fc = (new RandomAccessFile(f, "r")).getChannel();
+    fc.position(r.offset + addr - r.begin);
+    int nr = fc.read(b);
+    fc.close();
+    if (nr != length)
+      return null;
+    b.flip();
+    return new BytePtr(b, wordSize);
+  }
+  
+  public String getSymbol(long addr)
+  {
+    Range r = getRange(addr);
+    
+    if (r == null)
+      return null;
+    
+    SymbolTable st = symbolTables.get(r.filename);
+    if (st == null)
+      return null;
+    
+    // Apply relocation
+    addr -= st.relocation;
+    
+    return st.getSymbol(addr);
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/BlockMap.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/BlockMap.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/BlockMap.java	(revision 0)
@@ -0,0 +1,218 @@
+/* BlockMap.java -- Container for information on GC maintained memory blocks.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+class BlockMap
+{
+  static final int HBLKSIZE = 4096;
+
+  class SizeKind implements Comparable<SizeKind>
+  {
+    int size;
+    int kind;
+
+    public SizeKind(int size, int kind)
+    {
+      this.size = size;
+      this.kind = kind;
+    }
+
+    public int compareTo(SizeKind b)
+    {
+      if (this.size != b.size)
+        return this.size - b.size;
+      return this.kind - b.kind;
+    }
+  }
+
+  class PtrMarks
+  {
+    long ptr;
+    int marks;
+  
+    public PtrMarks(long ptr, int marks)
+    {
+      this.ptr = ptr;
+      this.marks = marks;
+    }
+  }
+
+  private TreeMap<SizeKind, ArrayList<PtrMarks>> map =
+    new TreeMap<SizeKind, ArrayList<PtrMarks>>();
+
+  public BlockMap(BufferedReader reader) throws IOException
+  {
+    for (;;)
+      {
+        String s = reader.readLine();
+        if (s == null)
+          break;
+        if (s.charAt(0) == '#')
+          continue;
+        if (s.indexOf("Begin block map") >= 0)
+          {
+            for (;;)
+              {
+                s = reader.readLine();
+                if (s.charAt(0) == '#')
+                  continue;
+                if (s.indexOf("End block map") >= 0)
+                  return;
+                String[] items = s.split(",");
+                long ptr = 0;
+                int kind = 0, size = 0, marks = 0;
+                for (int i=0; i<items.length; i++)
+                  {
+                    String[] x = items[i].split(" ");
+                    String last = x[x.length - 1];
+                    switch (i)
+                      {
+                      case 0:
+                        ptr = MemoryMap.parseHexLong(last.substring(2));
+                        break;
+                      case 1:
+                        kind = Integer.parseInt(last);
+                        break;
+                      case 2:
+                        size = Integer.parseInt(last);
+                        break;
+                      case 3:
+                        marks = Integer.parseInt(last);
+                        break;
+                      }
+                  }
+                SizeKind sk = new SizeKind(size, kind);
+                ArrayList<PtrMarks> m = map.get(sk);
+                if (m == null)
+                    {
+                        m = new ArrayList<PtrMarks>();
+                        map.put(sk, m);
+                    }
+                PtrMarks pm = new PtrMarks(ptr, marks);
+                m.add(pm);
+              } // inner loop
+          } // started inner loop
+      } // outer loop - finding begin
+  } // memoryMap
+
+  public void dump()
+  {
+    System.out.println();
+    System.out.println();
+    System.out.println("*** Used Blocks ***\n");
+    System.out.println();
+    System.out.println("  Size     Kind            Blocks     Used       Free       Wasted");
+    System.out.println("-------  -------------    ------- ---------- ----------    -------");
+
+    int total_blocks = 0, total_used = 0, total_free = 0, total_wasted = 0;
+
+    for (Map.Entry<SizeKind, ArrayList<PtrMarks>> me : map.entrySet())
+      {
+        SizeKind sk = me.getKey();
+
+        System.out.println(MemoryAnalyze.format(sk.size, 7) + "  "
+                           + MemoryAnalyze.kindToName(sk.kind));
+
+        int sub_blocks = 0, sub_used = 0, sub_free = 0, sub_wasted = 0;
+        int sub_count = 0;
+
+        ArrayList<PtrMarks> v = me.getValue();
+
+        for (PtrMarks pm : v)
+          {
+            int bytes = sk.size;
+            int blocks = (sk.size + HBLKSIZE - 1) / HBLKSIZE;
+            int used;
+            int free;
+            int wasted;
+
+            if (bytes < HBLKSIZE)
+              {
+                used = bytes * pm.marks;
+                free = bytes * (HBLKSIZE / bytes - pm.marks);
+                wasted = HBLKSIZE - HBLKSIZE / bytes * bytes;
+              }
+            else
+              {
+                if (pm.marks != 0)
+                  {
+                    used = bytes;
+                    free = 0;
+                    wasted = (bytes + HBLKSIZE - 1)
+                      / HBLKSIZE * HBLKSIZE - used;
+                  }
+                else
+                  {
+                    used = 0;
+                    free = bytes;
+                    wasted = 0;
+                  }
+              }
+
+            StringBuilder sb = new StringBuilder();
+            sb.append("                            ");
+            sb.append(MemoryAnalyze.format(blocks, 5));
+            sb.append("  ");
+            sb.append(MemoryAnalyze.format(used, 9));
+            sb.append("  ");
+            sb.append(MemoryAnalyze.format(free, 9));
+            sb.append("  ");
+            sb.append(MemoryAnalyze.format(wasted, 9));
+            System.out.println(sb);
+
+            sub_blocks += blocks;
+            sub_used += used;
+            sub_free += free;
+            sub_wasted += wasted;
+            sub_count++;
+
+            total_blocks += blocks;
+            total_used += used;
+            total_free += free;
+            total_wasted += wasted;
+          } // blocks with size/kind
+        if (sub_count > 1)
+          {
+            System.out.println(
+                               "                          ------- ---------- ----------    -------");
+            StringBuilder sb = new StringBuilder();
+            sb.append("                            ");
+            sb.append(MemoryAnalyze.format(sub_blocks, 5));
+            sb.append("  ");
+            sb.append(MemoryAnalyze.format(sub_used, 9));
+            sb.append("  ");
+            sb.append(MemoryAnalyze.format(sub_free, 9));
+            sb.append("  ");
+            sb.append(MemoryAnalyze.format(sub_wasted, 9));
+            System.out.println(sb);
+          }
+      } // size/kind
+
+    System.out.println("-------  -------------    ------- ---------- ----------    -------");
+    StringBuilder sb = new StringBuilder();
+    sb.append("                            ");
+    sb.append(MemoryAnalyze.format(total_blocks, 5));
+    sb.append("  ");
+    sb.append(MemoryAnalyze.format(total_used, 9));
+    sb.append("  ");
+    sb.append(MemoryAnalyze.format(total_free, 9));
+    sb.append("  ");
+    sb.append(MemoryAnalyze.format(total_wasted, 9));
+    System.out.println(sb);
+    System.out.println("Total bytes = "
+                       + MemoryAnalyze.format(total_blocks * HBLKSIZE, 10));
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/BytePtr.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/BytePtr.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/BytePtr.java	(revision 0)
@@ -0,0 +1,115 @@
+/* BytePtr.java -- Container for bytes from a memory image.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.nio.ByteBuffer;
+
+public class BytePtr
+{
+  ByteBuffer content;
+  int wordSize;
+
+  BytePtr(ByteBuffer b, int ws)
+  {
+    content = b;
+    wordSize = ws;
+  }
+  
+  public int getsize()
+  {
+    return content.limit();
+  }
+
+  public int getByte(int offset)
+  {
+    return content.get(offset);
+  }
+
+  public int getInt(int n)
+  {
+    return content.getInt(n * 4);
+  }
+
+  public int getShort(int n)
+  {
+    return content.getShort(n * 2);
+  }
+  
+  public long getWord(int n)
+  {
+    if (4 == wordSize)
+      return 0xffffffffL & content.getInt(n * 4);
+    else
+      return content.getLong(n * 8);
+  }
+  
+  public int intsPerWord()
+  {
+    return (4 == wordSize) ? 1 : 2;
+  }
+
+  public BytePtr getRegion(int offset, int size)
+  {
+    int oldLimit = content.limit();
+    content.position(offset);
+    content.limit(offset + size);
+    ByteBuffer n = content.slice();
+    content.position(0);
+    content.limit(oldLimit);
+    
+    return new BytePtr(n, wordSize);
+  }
+
+  public void setInt(int a, int n)
+  {
+    content.putInt(a * 4, n);
+  }
+
+  public void dump()
+  {
+    // 38 5a f4 2a 50 bd 04 10 10 00 00 00 0e 00 00 00   8Z.*P...........
+    int i;
+    StringBuilder b = new StringBuilder(67);
+    for (i = 0; i < 66; i++)
+      b.append(' ');
+    b.append('\n');
+
+    i = 0;
+    do
+      {
+        for (int j = 0; j < 16; j++)
+          {
+            int k = i + j;
+
+            if (k < content.limit())
+              {
+                int v = 0xff & getByte(k);
+                // hex
+                int v1 = v/16;
+                b.setCharAt(j * 3 + 0,
+                            (char)(v1 >= 10 ? 'a' - 10 + v1 : v1 + '0'));
+                v1 = v % 16;
+                b.setCharAt(j * 3 + 1,
+                            (char)(v1 >= 10 ? 'a' - 10 + v1 : v1 + '0'));
+                // ascii
+                b.setCharAt(j + 50, (char)((v >= 32 && v <= 127) ? v: '.'));
+              }
+            else
+              {
+                b.setCharAt(j * 3 + 0, ' ');
+                b.setCharAt(j * 3 + 1, ' ');
+                b.setCharAt(j + 50, ' ');
+              }
+          }
+        i += 16;
+        System.out.print(b);
+      } while (i < content.limit());
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/ItemList.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/ItemList.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/ItemList.java	(revision 0)
@@ -0,0 +1,72 @@
+/* ItemList.java -- Maps all objects keyed by their addresses.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+class ItemList
+{
+  public ItemList()
+  {
+  }
+
+  private TreeMap<Long, HashMap<ObjectMap.ObjectItem, Integer>> map;
+
+  public void add(ObjectMap.ObjectItem item)
+  {
+    if (map == null)
+      map = new TreeMap<Long, HashMap<ObjectMap.ObjectItem, Integer>>();
+    Long x = new Long(item.klass);
+    HashMap<ObjectMap.ObjectItem, Integer> list = map.get(x);
+    if (list == null)
+      {
+        list = new HashMap<ObjectMap.ObjectItem, Integer>();
+        map.put(x, list);
+      }
+    Integer count = list.get(item);
+    if (count == null)
+      list.put(item, new Integer(1));
+    else
+      list.put(item, new Integer(count.intValue() + 1));
+  }
+
+  void dump(String title, SymbolLookup lookup) throws IOException
+  {
+    if (map == null)
+      return;
+    System.out.println(title);
+    for (Map.Entry<Long, HashMap<ObjectMap.ObjectItem, Integer>> me :
+           map.entrySet())
+      {
+        HashMap<ObjectMap.ObjectItem, Integer> list = me.getValue();
+        boolean first = true;
+
+        for (Map.Entry<ObjectMap.ObjectItem, Integer> me2 : list.entrySet())
+          {
+            ObjectMap.ObjectItem item = me2.getKey();
+            Integer count = me2.getValue();
+            if (first)
+              {
+                String name =
+                  MemoryAnalyze.getSymbolPretty(lookup, item, false);
+                System.out.println("    " + name + ":");
+                first = false;
+              }
+            System.out.print("        0x" + Long.toHexString(item.ptr));
+            if (count.intValue() != 1)
+              System.out.print(" * " + count);
+            System.out.println();
+          }
+      }
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/ToolPrefix.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/ToolPrefix.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/ToolPrefix.java	(revision 0)
@@ -0,0 +1,45 @@
+/* ToolPrefix.java -- Container of the toolPrefix String.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.File;
+
+class ToolPrefix
+{
+  /**
+   * Private constructor.  No creation allowed.  This class has
+   * Static methods only.
+    */
+  private ToolPrefix()
+  {
+  }
+  
+  static String toolPrefix = "";
+
+  static String pathPrefix = "";
+  
+  static File fileForName(String filename)
+  {
+    File f = new File(pathPrefix + filename);
+    if (!f.canRead())
+      {
+        // Try it without the prefix.
+        f = new File(filename);
+        if (!f.canRead())
+          {
+            // Try to find it in the current directory.
+            f = new File(f.getName());
+            if (!f.canRead())
+              return null;
+          }      
+      }
+    return f;
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/MemoryAnalyze.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/MemoryAnalyze.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/MemoryAnalyze.java	(revision 0)
@@ -0,0 +1,458 @@
+/* MemoryAnalyze.java -- Analyzes a libgcj heap dump.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import gnu.classpath.tools.getopt.FileArgumentCallback;
+import gnu.classpath.tools.getopt.Option;
+import gnu.classpath.tools.getopt.OptionException;
+import gnu.classpath.tools.getopt.Parser;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+class MemoryAnalyze
+{
+  public MemoryAnalyze()
+  {
+  }
+
+  private static NumberFormat numberFormat;
+  private static boolean verbose;
+  static String format(long number, int digits)
+  {
+    if (numberFormat == null)
+      {
+        numberFormat = NumberFormat.getNumberInstance();
+        numberFormat.setGroupingUsed(true);
+      }
+    String temp = numberFormat.format(number);
+    int spaces = digits - temp.length();
+    if (spaces < 0)
+      spaces = 0;
+    return "                                ".substring(0,spaces) + temp;
+  }
+
+  static void sorted_report(String description,
+                            int total_space,
+                            ArrayList<String> list,
+                            Comparator<String> comparator)
+  {
+    System.out.println("*** " + description + " ***");
+    System.out.println();
+    System.out.println("  Total Size       Count       Size    Description");
+    System.out.println("--------------     -----    --------   -----------------------------------");
+    Collections.sort(list, comparator);
+    for (Iterator it = list.iterator(); it.hasNext(); )
+      {
+        String v = (String)it.next();
+        System.out.println(stripend(v));
+      }
+    System.out.println("--------------     -----    --------   -----------------------------------");
+    System.out.println(format(total_space, 14));
+    System.out.println();
+    System.out.println();
+  }
+
+  private static String stripend(String s)
+  {
+    int n = s.lastIndexOf(" /");
+    if (n > 0)
+      return s.substring(0,n);
+    return s;
+  }
+
+  static  class SubstringComparator implements Comparator<String>
+  {
+    private int begin, end;
+    private boolean reverse;
+
+    SubstringComparator(int begin, int end, boolean reverse)
+    {
+      this.begin = begin;
+      this.end = end;
+      this.reverse = reverse;
+    }
+
+    public int compare(String s1, String s2)
+    {
+      if (end == 0)
+        s1 = s1.substring(begin);
+      else
+        s1 = s1.substring(begin, end);
+
+      if (end == 0)
+        s2 = s2.substring(begin);
+      else
+        s2 = s2.substring(begin, end);
+      int i = s1.compareTo(s2);
+      if (reverse)
+        return -i;
+      return i;
+    }
+  }
+
+  static class OptionParser extends Parser
+  {
+    int filesFound;
+	  
+    OptionParser()
+    {
+      super("gc-analyze",
+            "gc-analyze (" + System.getProperty("java.vm.version") + ")");
+
+      add(new Option('d',
+                     "Directory containing runtime objects",
+                     "directory")
+        {
+          public void parsed(String argument) throws OptionException
+          {
+            ToolPrefix.pathPrefix = argument;			
+          }
+        });
+
+      add(new Option('p',
+                     "Binary tool prefix, prepended to nm and readelf to "
+                     + "obtain target specific versions of these commands",
+                     "prefix")
+        {
+          public void parsed(String argument) throws OptionException
+          {
+            ToolPrefix.toolPrefix = argument;			
+          }
+        });
+
+      add(new Option("verbose", 'v',
+                     "Verbose output; requires filename.bytes")
+        {
+          public void parsed(String argument) throws OptionException
+          {
+            verbose = true;			
+          }
+        });
+
+      setHeader("usage: gc-analyze [-v] [-p tool-prefix] [-d <directory>] "
+                + "filename");
+    }
+	  
+    protected void validate() throws OptionException
+    {
+      if (filesFound != 1)
+        throw new OptionException("Must specify exactly one filename");
+    }
+	  
+    public String[] parse(String[] inArgs)
+    {
+      final ArrayList<String> fileResult = new ArrayList<String>();
+      parse(inArgs, new FileArgumentCallback()
+        {
+          public void notifyFile(String fileArgument)
+          {
+            filesFound++;
+            fileResult.add(fileArgument);
+          }
+        });
+      return fileResult.toArray(new String[1]);
+    }
+  }
+  
+  public static void main(String[] args)
+  {
+    class Info
+    {
+      int size;
+      int count;
+    }
+    int total_space = 0;
+
+    Parser optionParser = new OptionParser();
+    
+    String rest[] = optionParser.parse(args);
+    
+    String filename = rest[0];
+    
+    try
+      {
+        BufferedReader reader =
+          new BufferedReader(new InputStreamReader(new FileInputStream(filename)));
+        SymbolLookup lookup = new SymbolLookup(reader, filename + ".bytes");
+        ObjectMap objectMap = new ObjectMap(reader);
+        BlockMap blockMap = new BlockMap(reader);
+        reader.close();
+
+        // add info to item(s)
+        // add item.klass
+        for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap)
+        {
+            ObjectMap.ObjectItem item = me.getValue();
+
+            // try to get a klass (happens with intern'ed strings...)
+            if (item.klass==0)
+              {
+                BytePtr p = lookup.getBytePtr(item.ptr, item.size);
+                if (p!=null)
+                  {
+                    long vtable = p.getWord(0);
+                    String sym =
+                        lookup.getSymbolViaVtable(vtable - 2 * lookup.memoryMap.wordSize);
+                    if (sym != null)
+                      {
+                        item.typeName = SymbolTable.demangleVTName(sym);
+                      }
+                    else if (vtable != 0)
+                      {
+                        // get klass from vtable
+                        p = lookup.getBytePtr(vtable,
+                                              lookup.memoryMap.wordSize);
+                        if (p != null)
+                          {
+                            long klass = p.getWord(0);
+                            item.klass = klass;
+                          }
+                      }
+                  }
+              }
+
+            // figure out strings
+            String class_name;
+            if (null == item.typeName)
+              {
+                class_name =
+                  MemoryAnalyze.getSymbolPretty(lookup, item, false);
+                item.typeName = class_name;
+              }
+            else
+              {
+                class_name = item.typeName;
+              }
+            System.out.print("class_name=[" + class_name + "]");
+
+            if (class_name.compareTo("_ZTVN4java4lang6StringE")==0
+                || class_name.compareTo("java.lang.String")==0)
+              {
+                BytePtr p = lookup.getBytePtr(item.ptr, item.size);
+                long data = p.getWord(1); 
+                int boffset = p.getInt(2 * p.intsPerWord());
+                int count = p.getInt(1 + 2 * p.intsPerWord());
+                int hash = p.getInt(2 + 2 * p.intsPerWord());
+                BytePtr chars = lookup.getBytePtr(data+boffset, count * 2);
+                StringBuffer sb = new StringBuffer(count);
+                for (int qq = 0; qq<count; qq++)
+                  sb.append((char)chars.getShort(qq));
+                int newhash = sb.toString().hashCode();
+                if (newhash!=hash)
+                  {
+                    p.setInt(4, newhash);
+                  }
+
+                item.string = sb.toString();
+                System.out.println(" value = \"" + item.string + "\"");
+                if (data != item.ptr)
+                  {
+                    ObjectMap.ObjectItem next = objectMap.get(data);
+                    if (next != null)
+                      next.stringData = true;
+                    else
+                      System.out.println("String [" + item.string + "] at "
+                                         + Long.toHexString(item.ptr)
+                                         + " can't find array at " 
+                                         + Long.toHexString(data));
+                  }
+              }
+            else if (null != item.string)
+              System.out.println(" value = \"" + item.string + "\"");
+            else
+              System.out.println();
+          }
+
+
+        HashMap<String, Info> map = new HashMap<String, Info>();
+        for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap)
+          {
+            ObjectMap.ObjectItem item = me.getValue();
+            String name = getSymbolPretty(lookup, item, true);
+            Info info = map.get(name);
+            if (info == null)
+              {
+                info = new Info();
+                info.count = 0;
+                info.size = item.size;
+                map.put(name, info);
+              }
+            info.count++;
+            total_space += item.size;
+          }
+
+        ArrayList<String> list = new ArrayList<String>();
+        for (Iterator it = map.entrySet().iterator(); it.hasNext(); )
+          {
+            Map.Entry me = (Map.Entry)it.next();
+            String name = (String)me.getKey();
+            Info info = (Info)me.getValue();
+
+            StringBuffer sb = new StringBuffer();
+            sb.append(format(info.count * info.size * 100 / total_space,
+                             3));
+            sb.append("%");
+            sb.append(format(info.count * info.size, 10));
+            sb.append(" = ");
+            sb.append(format(info.count, 7));
+            sb.append(" * ");
+            sb.append(format(info.size, 9));
+            sb.append(" - ");
+            sb.append(name);
+            list.add(sb.toString());
+          }
+
+        sorted_report("Memory Usage Sorted by Total Size",
+                      total_space, list, new SubstringComparator(5,14,true));
+        sorted_report("Memory Usage Sorted by Description",
+                      total_space, list, new SubstringComparator(39,0,false));
+        sorted_report("Memory Usage Sorted by Count",
+                      total_space, list, new SubstringComparator(17,25,true));
+        sorted_report("Memory Usage Sorted by Size",
+                      total_space, list, new SubstringComparator(28,37,true));
+
+        blockMap.dump();
+
+        // dump raw memory
+        if (verbose)
+          {
+            // analyze references
+            for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap)
+              {
+                long ptr = me.getKey();
+                ObjectMap.ObjectItem item = me.getValue();
+                BytePtr p = lookup.getBytePtr(ptr, item.size);
+                if (p == null)
+                  System.out.println("can't find ptr 0x"
+                                     + Long.toHexString(ptr));
+                else if (item.kind != 0) // not GC_PTRFREE
+                  for (int i = 1;
+                       i < item.size / lookup.memoryMap.wordSize; i++)
+                    {
+                      long maybe_ptr = p.getWord(i);
+                      ObjectMap.ObjectItem item2 = objectMap.get(maybe_ptr);
+                      if (item2 != null)
+                        {
+                          item2.pointed_by.add(item);
+                          item.points_to.add(item2);
+                        }
+                    }
+              }
+            System.out.println();
+            System.out.println("*** All Objects ***");
+            System.out.println();
+
+            for (Map.Entry<Long, ObjectMap.ObjectItem> me : objectMap)
+            {
+              long ptr = me.getKey();
+              ObjectMap.ObjectItem item = me.getValue();
+              String name = getSymbolPretty(lookup, item, false);
+              System.out.print("0x" + Long.toHexString(ptr) + " - " + name
+                               + " (" + item.size + ")");
+              if (item.string != null)
+        	System.out.println(" \"" + item.string + "\"");
+              else
+        	System.out.println();
+
+              BytePtr p = lookup.getBytePtr(ptr, item.size);
+
+              if (p == null)
+                System.out.println(
+                  "can't find memory; recently allocated from free list?");
+              else
+                p.dump();
+
+              item.points_to.dump("  points to:", lookup);
+              item.pointed_by.dump("  pointed to by:", lookup);
+              System.out.println();
+            }
+          }
+      }
+    catch (IOException e)
+      {
+        e.printStackTrace();
+      }
+  }
+
+  public static String kindToName(int kind)
+  {
+    String name;
+    switch (kind)
+      {
+      case 0:
+        name = "GC_PTRFREE";
+        break;
+      case 1:
+        name = "GC_NORMAL";
+        break;
+      case 2:
+        name = "GC_UNCOLLECTABLE";
+        break;
+      case 3:
+        name = "GC_AUUNCOLLCTABLE";
+        break;
+      case 4:
+        name = "(Java)";
+        break;
+      case 5:
+        name = "(Java Debug)";
+        break;
+      case 6:
+        name = "(Java Array)";
+        break;
+      default:
+        name = "(Kind " + kind + ")";
+        break;
+      }
+    return name;
+  }
+
+  public static String getSymbolPretty(SymbolLookup lookup,
+                                       ObjectMap.ObjectItem item,
+                                       boolean bsize)
+    throws IOException
+  {
+    
+    String name = item.typeName;
+    
+    if (name == null)
+      name = lookup.getSymbol(item.klass);
+    
+    if (name == null)
+      {
+      	String v = lookup.decodeUTF8(item.ptr, item.size);
+      	if (null != v)
+      	  {
+      	    name = "UTF8Const";
+      	    item.string = v;
+      	  }
+      }
+    
+    if (name == null)
+      {
+        name = kindToName(item.kind);
+      }
+    if (item.kind==6)
+      name += "[" + format(item.data, 0) + "]";
+    if (bsize)
+      name = name + " / " + format(item.size, 7);
+    return name;
+  }
+}
Index: libjava/gnu/gcj/tools/gc_analyze/SymbolTable.java
===================================================================
--- libjava/gnu/gcj/tools/gc_analyze/SymbolTable.java	(revision 0)
+++ libjava/gnu/gcj/tools/gc_analyze/SymbolTable.java	(revision 0)
@@ -0,0 +1,198 @@
+/* SymbolTable.java -- Maintains a mapping of addresses to names.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+package gnu.gcj.tools.gc_analyze;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class SymbolTable
+{
+  // Long address->String name
+  private HashMap<Long, String> map = new HashMap<Long, String>();
+
+  // Reverse
+  // String name -> Long address
+  // used for RelocateImage
+  private HashMap<String, Long> reverse = new HashMap<String, Long>();
+  
+  long loadAddr;
+  long relocation;
+
+  static Matcher interestingSymbol =
+    Pattern.compile("^([0-9a-fA-F]+)\\s+\\S+\\s+(_Z\\S+)").matcher("");
+  static Matcher readelfLoadMatcher =
+    Pattern.compile("^\\s+LOAD\\s+(\\S+)\\s+(\\S+)\\s.*").matcher("");
+ 
+  public SymbolTable(String filename) throws IOException
+  {
+    Process p = Runtime.getRuntime().exec(ToolPrefix.toolPrefix
+                                          + "nm " + filename);
+    InputStream es = p.getErrorStream();
+    InputStream is = p.getInputStream();
+
+    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+    int count = 0;
+
+    String line;
+    while ((line = reader.readLine()) != null)
+      {
+        interestingSymbol.reset(line);
+        if (interestingSymbol.matches())
+          {
+            try
+              {
+                String name = interestingSymbol.group(2);
+                String addr = interestingSymbol.group(1);
+                if (name.startsWith("_ZTVN") || name.endsWith("6class$E"))
+                  {
+                    long address = MemoryMap.parseHexLong(addr);
+                    Long l = new Long(address);
+                    map.put(l, name);
+                    count++;
+                    reverse.put(name, l);
+                  }
+              }
+            catch (NumberFormatException e)
+              {
+                // ignore it
+              }
+          }
+      }
+    es.close();
+    is.close();
+    p.destroy();
+    
+    if (count > 0)
+      {
+        // Assume nm read some symbols from it and that
+        // readelf can tell us something about how it is loaded.
+        p = Runtime.getRuntime().exec(ToolPrefix.toolPrefix
+                                      + "readelf -l " + filename);
+        es = p.getErrorStream();
+        is = p.getInputStream();
+
+        reader = new BufferedReader(new InputStreamReader(is));
+        while ((line = reader.readLine()) != null)
+          {
+            readelfLoadMatcher.reset(line);
+            if (readelfLoadMatcher.matches())
+              {
+                loadAddr
+                  = Long.decode(readelfLoadMatcher.group(2)).longValue();
+                break;
+              }
+          }
+        es.close();
+        is.close();
+        p.destroy();
+      }
+    
+    System.out.println(ToolPrefix.toolPrefix + "nm " + filename
+                       + " -> " + count + " symbols");
+  }
+
+  public static void main(String args[])
+  {
+    try
+      {
+        SymbolTable st = new SymbolTable(args[0]);
+        st.dump();
+      }
+    catch (Exception ex)
+      {
+        ex.printStackTrace();
+      }
+  }
+
+  public static String demangleVTName(String n)
+  {
+    if (n.startsWith("_ZTVN") && n.endsWith("E"))
+      return demangle(n.substring(5, n.length() - 1));
+    else
+      return null;
+  }
+
+  public void dump()
+  {
+    for (Map.Entry<Long, String> me : map.entrySet())
+      {
+        long address = me.getKey();
+        String symbol = me.getValue();
+        System.out.println(Long.toHexString(address) + " -> " + symbol);
+        if (symbol.startsWith("_ZN") && symbol.endsWith("6class$E"))
+          {
+            System.out.println("  Class: "
+                               + demangle(symbol.substring(3, symbol.length()
+                                                           - 8)));
+          }
+        else if (symbol.startsWith("_ZTVN") && symbol.endsWith("E"))
+          {
+            System.out.println("  VT: "
+                               + demangle(symbol.substring(5, symbol.length()
+                                                           - 1)));
+          }
+      }
+  }
+
+  private static String demangle(String symbol)
+  {
+    StringBuilder sb = new StringBuilder();
+    for (int i=0; i<symbol.length(); )
+      {
+        int l = 0;
+        while (i < symbol.length())
+          {
+            int d = symbol.charAt(i);
+            if (d < '0' || d > '9')
+              break;
+            l = 10 * l + (d - '0');
+            i++;
+          }
+        if (l == 0)
+          break; 
+        // copy
+        if (sb.length() > 0)
+          sb.append('.');
+        while (l > 0 && i < symbol.length())
+          {
+            sb.append(symbol.charAt(i));
+            l--;
+            i++;
+          }
+      }
+    return sb.toString();
+  }
+
+  public String getSymbol(long address)
+  {
+    String symbol = map.get(address);
+    if (symbol == null)
+      return null;
+
+    if (symbol.startsWith("_ZN") && symbol.endsWith("6class$E"))
+      symbol = demangle(symbol.substring(3, symbol.length() - 8));
+    return symbol;
+  }
+
+  // will return -1 if not found
+  public long getAddress(String symbol)
+  {
+    Long address = reverse.get(symbol);
+    if (address == null)
+      return -1;
+    return address.longValue();
+  }
+}
Index: libjava/gnu/gcj/util/GCInfo.java
===================================================================
--- libjava/gnu/gcj/util/GCInfo.java	(revision 0)
+++ libjava/gnu/gcj/util/GCInfo.java	(revision 0)
@@ -0,0 +1,79 @@
+/* GCInfo.java -- Support for creating heap dumps.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+package gnu.gcj.util;
+
+public class GCInfo
+{
+  private GCInfo()
+  {
+  }
+
+  /**
+   * @throws SecurityException if there is a SecurityManager installed
+   * and UtilPermission("dumpHeap") is not granted.
+   */
+  private static void checkPermission()
+  {
+    SecurityManager sm = System.getSecurityManager();
+    if (sm != null)
+      sm.checkPermission(new UtilPermission("dumpHeap"));
+  }
+  
+
+  /**
+   * Dump a description of the heap state.
+   *
+   * @param namePrefix The filename prefix for the dump files.
+   *
+   * @throws SecurityException if there is a SecurityManager installed
+   * and UtilPermission("dumpHeap") is not granted.
+   */
+  public static synchronized void dump(String name)
+  {
+    checkPermission();
+    dump0(name);
+  }
+  
+  private static native void dump0(String name);
+
+
+  /**
+   * Create a heap dump.
+   *
+   * @param namePrefix The filename prefix for the dump files.
+   *
+   * @throws SecurityException if there is a SecurityManager installed
+   * and UtilPermission("dumpHeap") is not granted.
+   */
+  public static synchronized void enumerate(String namePrefix)
+  {
+    checkPermission();
+    enumerate0(namePrefix);
+  }
+  
+  private static native void enumerate0(String namePrefix);
+
+  /**
+   * Cause a heap dump if out-of-memory condition occurs.
+   *
+   * @param namePrefix The filename prefix for the dump files.  If
+   * null no dumps are created.
+   *
+   * @throws SecurityException if there is a SecurityManager installed
+   * and UtilPermission("dumpHeap") is not granted.
+   */
+  public static synchronized void setOOMDump(String namePrefix)
+  {
+    checkPermission();
+    setOOMDump0(namePrefix);
+  }
+  
+  private static native void setOOMDump0(String namePrefix);
+}
Index: libjava/gnu/gcj/util/UtilPermission.java
===================================================================
--- libjava/gnu/gcj/util/UtilPermission.java	(revision 0)
+++ libjava/gnu/gcj/util/UtilPermission.java	(revision 0)
@@ -0,0 +1,20 @@
+/* GCInfo.java -- Support for creating heap dumps.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+package gnu.gcj.util;
+
+import java.security.BasicPermission;
+
+public class UtilPermission extends BasicPermission
+{
+  public UtilPermission(String name)
+  {
+    super(name);
+  }
+}
Index: libjava/gnu/gcj/util/natGCInfo.cc
===================================================================
--- libjava/gnu/gcj/util/natGCInfo.cc	(revision 0)
+++ libjava/gnu/gcj/util/natGCInfo.cc	(revision 0)
@@ -0,0 +1,454 @@
+/* natGCInfo.cc -- Native portion of support for creating heap dumps.
+   Copyright (C) 2007  Free Software Foundation
+
+   This file is part of libgcj.
+
+   This software is copyrighted work licensed under the terms of the
+   Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+   details.  */
+
+
+#include <config.h>
+
+#include <gcj/cni.h>
+
+#include <gnu/gcj/util/GCInfo.h>
+
+#ifdef HAVE_PROC_SELF_MAPS
+//
+// If /proc/self/maps does not exist we assume we are doomed and do nothing.
+//
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+//
+// Boehm GC includes.
+//
+#ifdef PACKAGE_NAME
+#undef PACKAGE_NAME
+#endif
+
+#ifdef PACKAGE_STRING
+#undef PACKAGE_STRING
+#endif
+
+#ifdef PACKAGE_TARNAME
+#undef PACKAGE_TARNAME
+#endif
+
+#ifdef PACKAGE_VERSION
+#undef PACKAGE_VERSION
+#endif
+
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+extern "C" {
+#include "private/dbg_mlc.h"
+  int GC_n_set_marks(hdr* hhdr);
+  ptr_t GC_clear_stack(ptr_t p);
+  extern int GC_gcj_kind;
+  extern int GC_gcj_debug_kind;
+}
+
+#endif
+
+#ifdef HAVE_PROC_SELF_MAPS
+
+static int gc_ok = 1;
+
+typedef struct gc_debug_info
+{
+  int used;
+  int free;
+  int wasted;
+  int blocks;
+  FILE* fp;
+};
+
+static void
+GC_print_debug_callback(hblk *h, word user_data)
+{
+  hdr *hhdr = HDR(h);
+  size_t bytes = WORDS_TO_BYTES(hhdr -> hb_sz);
+
+  gc_debug_info *pinfo = (gc_debug_info *)user_data;
+
+  fprintf(pinfo->fp, "ptr = %#lx, kind = %d, size = %zd, marks = %d\n",
+          (unsigned long)h, hhdr->hb_obj_kind, bytes, GC_n_set_marks(hhdr));
+}
+
+/*
+  this next section of definitions shouldn't really be here.
+  copied from boehmgc/allchblk.c
+*/
+
+# define UNIQUE_THRESHOLD 32
+# define HUGE_THRESHOLD 256
+# define FL_COMPRESSION 8
+# define N_HBLK_FLS (HUGE_THRESHOLD - UNIQUE_THRESHOLD)/FL_COMPRESSION \
+                         + UNIQUE_THRESHOLD
+#ifndef USE_MUNMAP
+extern "C" {
+  extern word GC_free_bytes[N_HBLK_FLS+1];
+}
+#endif
+
+# ifdef USE_MUNMAP
+#   define IS_MAPPED(hhdr) (((hhdr) -> hb_flags & WAS_UNMAPPED) == 0)
+# else  /* !USE_MMAP */
+#   define IS_MAPPED(hhdr) 1
+# endif /* USE_MUNMAP */
+
+static void
+GC_print_hblkfreelist_file(FILE *fp)
+{
+  struct hblk * h;
+  word total_free = 0;
+  hdr * hhdr;
+  word sz;
+  int i;
+    
+  fprintf(fp, "---------- Begin free map ----------\n");
+  for (i = 0; i <= N_HBLK_FLS; ++i)
+    {
+      h = GC_hblkfreelist[i];
+#ifdef USE_MUNMAP
+      if (0 != h)
+        fprintf (fp, "Free list %ld:\n", (unsigned long)i);
+#else
+      if (0 != h)
+        fprintf (fp, "Free list %ld (Total size %ld):\n",
+                 (unsigned long)i,
+                 (unsigned long)GC_free_bytes[i]);
+#endif
+      while (h != 0)
+        {
+          hhdr = HDR(h);
+          sz = hhdr -> hb_sz;
+          fprintf (fp, "\t0x%lx size %lu ", (unsigned long)h,
+                   (unsigned long)sz);
+          total_free += sz;
+
+          if (GC_is_black_listed (h, HBLKSIZE) != 0)
+            fprintf (fp, "start black listed\n");
+          else if (GC_is_black_listed(h, hhdr -> hb_sz) != 0)
+            fprintf (fp, "partially black listed\n");
+          else
+            fprintf (fp, "not black listed\n");
+
+          h = hhdr -> hb_next;
+        }
+    }
+#ifndef USE_MUNMAP
+  if (total_free != GC_large_free_bytes)
+    {
+      fprintf (fp, "GC_large_free_bytes = %lu (INCONSISTENT!!)\n",
+               (unsigned long) GC_large_free_bytes);
+    }
+#endif
+  fprintf (fp, "Total of %lu bytes on free list\n", (unsigned long)total_free);
+  fprintf (fp, "---------- End free map ----------\n");
+}
+
+static int GC_dump_count = 1;
+
+static void
+GC_print_debug_info_file(FILE* fp)
+{
+  gc_debug_info info;
+
+  memset(&info, 0, sizeof info);
+  info.fp = fp;
+
+  if (gc_ok)
+    GC_gcollect();
+  fprintf(info.fp, "---------- Begin block map ----------\n");
+  GC_apply_to_all_blocks(GC_print_debug_callback, (word)(void*)(&info));
+  //fprintf(fp, "#Total used %d free %d wasted %d\n", info.used, info.free, info.wasted);
+  //fprintf(fp, "#Total blocks %d; %dK bytes\n", info.blocks, info.blocks*4);
+  fprintf(info.fp, "---------- End block map ----------\n");
+
+  //fprintf(fp, "\n***Free blocks:\n");
+  //GC_print_hblkfreelist();
+}
+
+namespace
+{
+  class  __attribute__ ((visibility ("hidden"))) GC_enumerator
+  {
+  public:
+    GC_enumerator(const char *name);
+    void enumerate();
+  private:
+    FILE* fp;
+    int bytes_fd;
+
+    void print_address_map();
+    void enumerate_callback(struct hblk *h);
+    static void enumerate_callback_adaptor(struct hblk *h, word dummy);
+  };
+}
+
+GC_enumerator::GC_enumerator(const char *name)
+{
+  bytes_fd = -1;
+  fp = fopen (name, "w");
+  if (!fp)
+    {
+      printf ("GC_enumerator failed to open [%s]\n", name);
+      return;
+    }
+  printf ("GC_enumerator saving summary to [%s]\n", name);
+
+  // open heap file
+  char bytes_name[strlen(name) + 10];
+  sprintf (bytes_name, "%s.bytes", name);
+  bytes_fd = open (bytes_name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+  if (bytes_fd <= 0)
+    {
+      printf ("GC_enumerator failed to open [%s]\n", bytes_name);
+      return;
+    }
+  printf ("GC_enumerator saving heap contents to [%s]\n", bytes_name);
+}
+
+/*
+  sample format of /proc/self/maps
+
+  0063b000-00686000 rw-p 001fb000 03:01 81993      /avtrex/bin/dumppropapp
+  00686000-0072e000 rwxp 00000000 00:00 0 
+
+  These are parsed below as:
+  start   -end      xxxx xxxxxxxx  a:b xxxxxxxxxxxxxxx
+
+*/
+
+
+void
+GC_enumerator::print_address_map()
+{
+  FILE* fm;
+  char buffer[128];
+
+  fprintf(fp, "---------- Begin address map ----------\n");
+
+  fm = fopen("/proc/self/maps", "r");
+  if (fm == NULL)
+    {
+      if (0 == strerror_r (errno, buffer, sizeof buffer))
+        fputs (buffer, fp);
+    }
+  else
+    {
+      while (fgets (buffer, sizeof buffer, fm) != NULL)
+        {
+          fputs (buffer, fp);
+          char *dash = strchr(buffer, '-');
+          char *colon = strchr(buffer, ':');
+          if (dash && colon && ((ptrdiff_t)strlen(buffer) > (colon - buffer) + 2))
+            {
+              char *endp;
+              unsigned long start = strtoul(buffer, NULL, 16);
+              unsigned long end   = strtoul(dash + 1, &endp, 16);
+              unsigned long a     = strtoul(colon - 2, NULL, 16);
+              unsigned long b     = strtoul(colon + 1, NULL, 16);
+              // If it is an anonymous mapping 00:00 and both readable
+              // and writeable then dump the contents of the mapping
+              // to the bytes file.  Each block has a header of three
+              // unsigned longs:
+              // 0 - The number sizeof(unsigned long) to detect endianness and
+              //     structure layout.
+              // 1 - The offset in VM.
+              // 2 - The Length in bytes.
+              // Followed by the bytes.
+              if (!a && !b && endp < colon && 'r' == endp[1] && 'w' == endp[2])
+                {
+                  unsigned long t = sizeof(unsigned long);
+                  write(bytes_fd, (void*)&t, sizeof(t));
+                  write(bytes_fd, (void*)&start, sizeof(start));
+                  t = end - start;
+                  write(bytes_fd, (void*)&t, sizeof(t));
+                  write(bytes_fd, (void*)start, (end - start));
+                }
+            }
+        } 
+      fclose(fm);
+    }
+  fprintf(fp, "---------- End address map ----------\n");
+  fflush(fp);
+}
+
+void
+GC_enumerator::enumerate()
+{
+  print_address_map();
+  fprintf(fp, "---------- Begin object map ----------\n");
+  if (gc_ok)
+    GC_gcollect();
+  GC_apply_to_all_blocks(enumerate_callback_adaptor, 
+                         (word)(void*)(this));
+  fprintf(fp, "---------- End object map ----------\n");
+  fflush(fp); 
+
+  GC_print_debug_info_file(fp);
+  fflush(fp); 
+  GC_print_hblkfreelist_file(fp);
+  fflush(fp); 
+
+  close(bytes_fd);
+  fclose(fp);
+
+  GC_clear_stack(0);
+}
+
+void
+GC_enumerator::enumerate_callback_adaptor(struct hblk *h,
+                                          word dummy)
+{
+  GC_enumerator* pinfo = (GC_enumerator*)dummy;
+  pinfo->enumerate_callback(h);
+}
+
+void
+GC_enumerator::enumerate_callback(struct hblk *h)
+{
+  hdr * hhdr = HDR(h);
+  size_t bytes = WORDS_TO_BYTES(hhdr->hb_sz);
+  int i;
+
+  for (i = 0; i == 0 || (i + bytes <= HBLKSIZE); i += bytes)
+    {
+      int inUse = mark_bit_from_hdr(hhdr,BYTES_TO_WORDS(i));  // in use
+      char *ptr = (char*)h+i;                                 // address
+      int kind = hhdr->hb_obj_kind;                           // kind
+      void *klass = 0;
+      void *data = 0;
+      if (kind == GC_gcj_kind
+          || kind == GC_gcj_debug_kind
+          || kind == GC_gcj_debug_kind+1)
+        {
+          void* v = *(void **)ptr;
+          if (v)
+            {
+              klass = *(void **)v;
+              data = *(void **)(ptr + sizeof(void*));
+            }
+        }
+      if (inUse)
+        fprintf (fp, "used = %d, ptr = %#lx, size = %zd, kind = %d, "
+                 "klass = %#lx, data = %#lx\n", 
+                 inUse, (unsigned long)ptr, bytes, kind,
+                 (unsigned long)klass, (unsigned long)data);
+    }
+}
+
+/*
+ * Fill in a char[] with low bytes of the string characters.  These
+ * methods may be called while an OutOfMemoryError is being thrown, so
+ * we cannot call nice java methods to get the encoding of the string.
+ */
+static void
+J2A(::java::lang::String* str, char *dst)
+{
+  jchar * pchars = JvGetStringChars(str);
+  jint len = str->length();
+  int i;
+  for (i=0; i<len; i++)
+    dst[i] = (char)pchars[i];
+  dst[i] = 0;
+}
+
+void
+::gnu::gcj::util::GCInfo::dump0 (::java::lang::String * name)
+{
+  char n[name->length() + 1];
+  J2A(name, n);
+  
+  char temp[name->length() + 20];
+  sprintf(temp, "%s%03d", n, GC_dump_count++);
+  FILE* fp = fopen(temp, "w");
+
+  GC_print_debug_info_file(fp);
+
+  fclose(fp);
+}
+
+void
+::gnu::gcj::util::GCInfo::enumerate0 (::java::lang::String * name)
+{
+  char n[name->length() + 1];
+  J2A(name, n);
+  char temp[name->length() + 20];
+  sprintf(temp, "%s%03d", n, GC_dump_count++);
+
+  GC_enumerator x(temp);
+  x.enumerate();
+}
+
+static char *oomDumpName = NULL;
+
+static void *
+nomem_handler(size_t size)
+{
+  if (oomDumpName)
+    {
+      char temp[strlen(oomDumpName) + 20];
+      sprintf(temp, "%s%03d", temp, GC_dump_count++);
+      printf("nomem_handler(%zd) called\n", size);
+      gc_ok--;
+      GC_enumerator x(temp);
+      x.enumerate();
+      gc_ok++;
+    }
+  return (void*)0;
+}
+
+void
+::gnu::gcj::util::GCInfo::setOOMDump0 (::java::lang::String * name)
+{
+  char *oldName = oomDumpName;
+  oomDumpName = NULL;
+  free (oldName);
+  
+  if (NULL == name)
+    return;
+  
+  char *n = (char *)malloc(name->length() + 1);
+
+  J2A(name, n);
+  oomDumpName = n;
+  GC_oom_fn = nomem_handler;
+}
+
+#else  // HAVE_PROC_SELF_MAPS
+
+void
+::gnu::gcj::util::GCInfo::dump0 (::java::lang::String * name)
+{
+  // Do nothing if dumping not supported.
+}
+
+void
+::gnu::gcj::util::GCInfo::enumerate0 (::java::lang::String * name)
+{
+  // Do nothing if dumping not supported.
+}
+
+void
+::gnu::gcj::util::GCInfo::setOOMDump0 (::java::lang::String * name)
+{
+  // Do nothing if dumping not supported.
+}
+
+#endif // HAVE_PROC_SELF_MAPS
+
Index: libjava/Makefile.am
===================================================================
--- libjava/Makefile.am	(revision 121878)
+++ libjava/Makefile.am	(working copy)
@@ -73,7 +73,7 @@ db_pathtail = gcj-$(gcc_version)/$(db_na
 if NATIVE
 bin_PROGRAMS = jv-convert gij grmic grmiregistry gcj-dbtool \
 	gappletviewer gjarsigner gkeytool gjar gjavah gnative2ascii \
-	gorbd grmid gserialver gtnameserv
+	gorbd grmid gserialver gtnameserv gc-analyze
 
 ## It is convenient to actually build and install the default database
 ## when gcj-dbtool is available.
@@ -647,6 +647,20 @@ gcj_dbtool_LDADD = gnu/gcj/tools/gcj_dbt
 ## linking this program.
 gcj_dbtool_DEPENDENCIES = gnu/gcj/tools/gcj_dbtool.lo libgcj.la libgcj.spec
 
+gc_analyze_SOURCES =
+
+## We need -nodefaultlibs because we want to avoid gcj's `-lgcj'.  We
+## need this because we are explicitly using libtool to link using the
+## `.la' file.
+gc_analyze_LDFLAGS = --main=gnu.gcj.tools.gc_analyze.MemoryAnalyze \
+        -rpath $(toolexeclibdir) -shared-libgcc $(THREADLDFLAGS)
+gc_analyze_LINK = $(GCJLINK)
+## See jv_convert_LDADD.
+gc_analyze_LDADD = -L$(here)/.libs libgcj-tools.la libgcj.la
+## Depend on the spec file to make sure it is up to date before
+## linking this program.
+gc_analyze_DEPENDENCIES = libgcj-tools.la libgcj.la libgcj.spec
+
 gij_SOURCES = 
 ## We need -nodefaultlibs because we want to avoid gcj's `-lgcj'.  We
 ## need this because we are explicitly using libtool to link using the
@@ -831,6 +845,7 @@ gnu/gcj/runtime/natSharedLibLoader.cc \
 gnu/gcj/runtime/natSystemClassLoader.cc \
 gnu/gcj/runtime/natStringBuffer.cc \
 gnu/gcj/util/natDebug.cc \
+gnu/gcj/util/natGCInfo.cc \
 gnu/java/lang/natMainThread.cc \
 gnu/java/lang/management/natVMClassLoadingMXBeanImpl.cc \
 gnu/java/lang/management/natVMCompilationMXBeanImpl.cc \

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]