From aac85ff2786190163b3809866f15843824b9e39f Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Sun, 7 Nov 2010 12:57:42 -0700 Subject: [PATCH 1/9] remove @Overload annotation since Java 1.5.0_19 chokes on it --- classpath/java/util/Collections.java | 1 - 1 file changed, 1 deletion(-) diff --git a/classpath/java/util/Collections.java b/classpath/java/util/Collections.java index e790c4ed70..eb7c5d462c 100644 --- a/classpath/java/util/Collections.java +++ b/classpath/java/util/Collections.java @@ -393,7 +393,6 @@ public class Collections { this.cmp = cmp; } - @Override public int compare(T o1, T o2) { return - cmp.compare(o1, o2); } From 86ed206f5a3a83f297ceecd07eab08c6251b71d0 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 18 Nov 2010 10:19:48 -0700 Subject: [PATCH 2/9] remove temporary debug code from posix.cpp --- src/posix.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/posix.cpp b/src/posix.cpp index a8e92eb8e4..da0397c107 100644 --- a/src/posix.cpp +++ b/src/posix.cpp @@ -782,7 +782,6 @@ class MySystem: public System { } virtual void abort() { - *static_cast(0) = 0; ::abort(); } From 7b85afedecdd0801d6a4414c6ef6a24467b2668c Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 18 Nov 2010 10:24:58 -0700 Subject: [PATCH 3/9] ensure that sa_sigaction is non-null before attempting to call it --- src/posix.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/posix.cpp b/src/posix.cpp index da0397c107..e5ec88b0a1 100644 --- a/src/posix.cpp +++ b/src/posix.cpp @@ -874,7 +874,9 @@ handleSignal(int signal, siginfo_t* info, void* context) default: abort(); } - if (system->oldHandlers[index].sa_flags & SA_SIGINFO) { + if (system->oldHandlers[index].sa_flags & SA_SIGINFO + and system->oldHandlers[index].sa_sigaction) + { system->oldHandlers[index].sa_sigaction(signal, info, context); } else if (system->oldHandlers[index].sa_handler) { system->oldHandlers[index].sa_handler(signal); From 19dbc61e9f8324fe159acd1621bc5c84eea8e39d Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 18 Nov 2010 10:55:00 -0700 Subject: [PATCH 4/9] for heapdump=true builds, optionally generate dump on OOM If the VM runs out of heap space and the "avian.heap.dump" system property was specified at startup, the VM will write a heap dump to the filename indicated by that property. This dump may be analyzed using e.g. DumpStats.java. --- src/heap.cpp | 24 +++++++++++++++++++----- src/heap.h | 1 + src/machine.cpp | 15 +++++++++++++++ test/extra/DumpStats.java | 5 +++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/heap.cpp b/src/heap.cpp index 718d2885a0..02211204b2 100644 --- a/src/heap.cpp +++ b/src/heap.cpp @@ -69,6 +69,7 @@ void assert(Context*, bool); System* system(Context*); void* tryAllocate(Context* c, unsigned size); void free(Context* c, const void* p, unsigned size); +void outOfMemory(Context*); #ifdef USE_ATOMIC_OPERATIONS inline void @@ -359,7 +360,7 @@ class Segment { break; } } else { - abort(context); + outOfMemory(context); } } } @@ -1708,7 +1709,8 @@ collect(Context* c) } } -void* tryAllocate(Context* c, unsigned size) +void* +tryAllocate(Context* c, unsigned size) { ACQUIRE(c->lock); @@ -1733,7 +1735,9 @@ void* tryAllocate(Context* c, unsigned size) return 0; } -void free(Context* c, const void* p, unsigned size) { +void +free(Context* c, const void* p, unsigned size) +{ ACQUIRE(c->lock); if (DebugAllocation) { @@ -1755,10 +1759,18 @@ void free(Context* c, const void* p, unsigned size) { c->count -= size; } -void free_(Context* c, const void* p, unsigned size) { +void +free_(Context* c, const void* p, unsigned size) +{ free(c, p, size); } +void +outOfMemory(Context* c) +{ + c->client->outOfMemory(); +} + class MyHeap: public Heap { public: MyHeap(System* system, unsigned limit): @@ -1781,7 +1793,9 @@ class MyHeap: public Heap { virtual void* allocate(unsigned size) { void* p = local::tryAllocate(&c, size); - expect(c.system, p); + if (p == 0) { + c.client->outOfMemory(); + } return p; } diff --git a/src/heap.h b/src/heap.h index 769bc1ec4b..6b392a1de6 100644 --- a/src/heap.h +++ b/src/heap.h @@ -49,6 +49,7 @@ class Heap: public Allocator { virtual unsigned copiedSizeInWords(void*) = 0; virtual void copy(void*, void*) = 0; virtual void walk(void*, Walker*) = 0; + virtual void outOfMemory() = 0; }; virtual void setClient(Client* client) = 0; diff --git a/src/machine.cpp b/src/machine.cpp index d0b82f87fb..4684fca888 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -2081,6 +2081,21 @@ class HeapClient: public Heap::Client { ::walk(m->rootThread, w, o, 0); } + virtual void outOfMemory() { +#ifdef AVIAN_HEAPDUMP + const char* path = findProperty(m->rootThread, "avian.heap.dump"); + if (path) { + FILE* out = vm::fopen(path, "wb"); + if (out) { + dumpHeap(m->rootThread, out); + fclose(out); + } + } +#endif//AVIAN_HEAPDUMP + + abort(m->system); + } + void dispose() { m->heap->free(this, sizeof(*this)); } diff --git a/test/extra/DumpStats.java b/test/extra/DumpStats.java index c9e7774484..470d22656a 100644 --- a/test/extra/DumpStats.java +++ b/test/extra/DumpStats.java @@ -16,8 +16,9 @@ import java.util.Arrays; * heap dump generated by Avian's heapdump.cpp. The output is a list * of classes (identified by number in the case of anonymous, * VM-internal classes), each followed by (1) the total memory - * footprint of all instances of the class, and (2) the number of - * instances. The output is ordered by instance memory footprint. + * footprint of all instances of the class in machine words, and (2) + * the number of instances. The output is ordered by instance memory + * footprint. */ public class DumpStats { private static final int Root = 0; From f9197cb07692b067030999d670b37fd4188b071c Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Sun, 21 Nov 2010 17:26:17 -0700 Subject: [PATCH 5/9] fix a few HashMap bugs 1. HashMap.containsValue only checked one hash bucket, which was pretty much useless :) 2. HashMap.MyIterator.remove was broken in that it failed to decrement the size field and it did not update the previousCell field properly, which sometimes led to more than one cell being removed. --- classpath/java/util/HashMap.java | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/classpath/java/util/HashMap.java b/classpath/java/util/HashMap.java index 0f00f920b2..e767b166d8 100644 --- a/classpath/java/util/HashMap.java +++ b/classpath/java/util/HashMap.java @@ -149,16 +149,17 @@ public class HashMap implements Map { } public boolean containsValue(Object value) { - if (array != null) { - int index = array.length - 1; - for (Cell c = array[index]; c != null; c = c.next()) { - if (helper.equal(value, c.getValue())) { - return true; + if (array != null) { + for (int i = 0; i < array.length; ++i) { + for (Cell c = array[i]; c != null; c = c.next()) { + if (helper.equal(value, c.getValue())) { + return true; + } } } } - return false; + return false; } public V get(Object key) { @@ -450,10 +451,12 @@ public class HashMap implements Map { public Entry next() { if (hasNext()) { - if (currentCell != null && currentCell.next() != null) { - previousCell = currentCell; - } else { - previousCell = null; + if (currentCell != null) { + if (currentCell.next() != null) { + previousCell = currentCell; + } else { + previousCell = null; + } } currentCell = nextCell; @@ -490,6 +493,7 @@ public class HashMap implements Map { } } currentCell = null; + -- size; } else { throw new IllegalStateException(); } From 878e98954b360efaea231da88ce4f07aa30ca0dd Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 22 Nov 2010 10:04:28 -0700 Subject: [PATCH 6/9] add QueryDump heap dump analysis tool --- test/extra/DumpStats.java | 15 +- test/extra/QueryDump.java | 358 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 test/extra/QueryDump.java diff --git a/test/extra/DumpStats.java b/test/extra/DumpStats.java index 470d22656a..73b9170bed 100644 --- a/test/extra/DumpStats.java +++ b/test/extra/DumpStats.java @@ -108,8 +108,16 @@ public class DumpStats { return map; } + + private static void usageAndExit() { + System.err.println("usage: java DumpStats "); + } public static void main(String[] args) throws Exception { + if (args.length != 2) { + usageAndExit(); + } + Map map = read (new BufferedInputStream(new FileInputStream(args[0]))); @@ -120,19 +128,22 @@ public class DumpStats { } }); + int wordSize = Integer.parseInt(args[1]); + int footprint = 0; int count = 0; for (Record r: array) { if (r.name == null) { r.name = String.valueOf(r.key); } - System.out.println(r.name + ": " + r.footprint + " " + r.count); + System.out.println + (r.name + ": " + (r.footprint * wordSize) + " " + r.count); footprint += r.footprint; count += r.count; } System.out.println(); - System.out.println("total: " + footprint + " " + count); + System.out.println("total: " + (footprint * wordSize) + " " + count); } private static class Record { diff --git a/test/extra/QueryDump.java b/test/extra/QueryDump.java new file mode 100644 index 0000000000..156cd64d36 --- /dev/null +++ b/test/extra/QueryDump.java @@ -0,0 +1,358 @@ +package extra; + +import java.io.PrintStream; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.EOFException; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.Comparator; +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +public class QueryDump { + private static final int Root = 0; + private static final int Size = 1; + private static final int ClassName = 2; + private static final int Push = 3; + private static final int Pop = 4; + + private static int readInt(InputStream in) throws IOException { + int b1 = in.read(); + int b2 = in.read(); + int b3 = in.read(); + int b4 = in.read(); + if (b4 == -1) throw new EOFException(); + return (int) ((b1 << 24) | (b2 << 16) | (b3 << 8) | (b4)); + } + + private static String readString(InputStream in) throws IOException { + int count = readInt(in); + byte[] b = new byte[count]; + int offset = 0; + int c; + while ((c = in.read(b, offset, b.length - offset)) != -1 + && offset < b.length) + { + offset += c; + } + if (offset != b.length) throw new EOFException(); + return new String(b); + } + + private static Record record(Map map, int key) { + Record r = map.get(key); + if (r == null) { + map.put(key, r = new Record(key)); + } + return r; + } + + private static void push(List stack, T value) { + stack.add(value); + } + + private static T pop(List stack) { + return stack.remove(stack.size() - 1); + } + + private static T peek(List stack, int offset) { + return stack.get(stack.size() - 1 - offset); + } + + private static T peek(List stack) { + return peek(stack, 0); + } + + private static Set nodes(Record record) { + if (record.nodes == null) { + record.nodes = new HashSet(2); + } + return record.nodes; + } + + private static void query(Map nodes, Record[] query, + List stack, int index) + { + Node node = nodes.get(peek(stack, index).key); + if (node != null) { + int base = node.index(); + for (int i = base + 1; i < query.length; ++i) { + int peek = index + i - base; + if (peek < stack.size()) { + Instance instance = peek(stack, peek); + if (query[i] == instance.record) { + TreeNode next = (TreeNode) nodes.get(instance); + if (next == null) { + nodes.put(instance.key, next = new TreeNode(instance, i)); + } + next.children.add(node); + node = next; + } else { + return; + } + } else { + return; + } + } + + if (index + query.length - base < stack.size()) { + nodes(peek(stack, index + query.length - base).record).add(node); + } + } + } + + private static void query(Map nodes, Record[] query, + List stack) + { + if (stack.size() > 1) { + Instance instance = peek(stack, 1); + if (instance != null && instance.record == query[0]) { + Node node = nodes.get(instance.key); + if (node == null) { + nodes.put(instance.key, new LeafNode(instance)); + query(nodes, query, stack, 1); + } + return; + } + } + + query(nodes, query, stack, 0); + } + + private static Map read(InputStream in, + String[] queryClasses) + throws IOException + { + boolean done = false; + boolean popped = false; + Map records = new HashMap(); + Map nodes = new HashMap(); + List stack = new ArrayList(); + Record[] query = new Record[queryClasses.length]; + + Record roots = new Record(-1, ""); + records.put(roots.key, roots); + + while (! done) { + int flag = in.read(); + switch (flag) { + case Root: { + stack.clear(); + push(stack, new Instance(readInt(in))); + + query(nodes, query, stack); + + popped = false; + // System.out.println("root " + last); + } break; + + case ClassName: { + String name = readString(in); + Record r = record(records, peek(stack).key); + r.name = name; + + for (int i = 0; i < queryClasses.length; ++i) { + if (queryClasses[i].equals(name)) { + query[i] = r; + } + } + + query(nodes, query, stack); + } break; + + case Push: { + int key = readInt(in); + + if (! popped) { + peek(stack).record = record(records, key); + } + + push(stack, new Instance(key)); + + query(nodes, query, stack); + + popped = false; + } break; + + case Pop: { + pop(stack); + + popped = true; + } break; + + case Size: { + peek(stack).size = readInt(in); + } break; + + case -1: + done = true; + break; + + default: + throw new RuntimeException("bad flag: " + flag); + } + } + + return records; + } + + private static String[] copy(String[] array, int offset, int length) { + String[] copy = new String[length]; + if (length > 0) { + System.arraycopy(array, offset, copy, 0, length); + } + + return copy; + } + + private static void visitLeaves(Set nodes, LeafVisitor visitor) { + for (Node n: nodes) { + n.visitLeaves(visitor); + } + } + + private static void usageAndExit() { + System.err.println("usage: java QueryDump " + + " ..."); + } + + public static void main(String[] args) throws Exception { + if (args.length < 3) { + usageAndExit(); + } + + Map map = read + (new BufferedInputStream(new FileInputStream(args[0])), + copy(args, 2, args.length - 2)); + + for (Iterator it = map.values().iterator(); it.hasNext();) { + final Record r = it.next(); + if (r.nodes == null) { + it.remove(); + } else { + visitLeaves(r.nodes, new LeafVisitor() { + private Set set = new HashSet(); + + public void visit(LeafNode node) { + if (! set.contains(node.instance)) { + r.footprint += node.instance.size; + ++ r.count; + } + set.add(node.instance); + } + }); + } + } + + Record[] array = map.values().toArray(new Record[map.size()]); + Arrays.sort(array, new Comparator() { + public int compare(Record a, Record b) { + return b.footprint - a.footprint; + } + }); + + int wordSize = Integer.parseInt(args[1]); + + int footprint = 0; + int count = 0; + for (Record r: array) { + if (r.name == null) { + r.name = String.valueOf(r.key); + } + System.out.println + (r.name + ": " + (r.footprint * wordSize) + " " + r.count); + footprint += r.footprint; + count += r.count; + } + + System.out.println(); + System.out.println("total: " + (footprint * wordSize) + " " + count); + } + + private static class Record { + public final int key; + public String name; + public int footprint; + public int count; + public Set nodes; + + public Record(int key) { + this(key, null); + } + + public Record(int key, String name) { + this.key = key; + this.name = name; + } + + public String toString() { + return name; + } + } + + private static class Instance { + public final int key; + public int size; + public Record record; + + public Instance(int key) { + this.key = key; + } + + public String toString() { + return "[" + key + " " + record + "]"; + } + } + + public interface Node { + public void visitLeaves(LeafVisitor visitor); + public int index(); + } + + public static class LeafNode implements Node { + public final Instance instance; + + public LeafNode(Instance instance) { + this.instance = instance; + } + + public void visitLeaves(LeafVisitor visitor) { + visitor.visit(this); + } + + public int index() { + return 0; + } + } + + public static class TreeNode implements Node { + public final Instance instance; + public final int index; + + public final Set children = new HashSet(2); + + public TreeNode(Instance instance, int index) { + this.instance = instance; + this.index = index; + } + + public void visitLeaves(LeafVisitor visitor) { + QueryDump.visitLeaves(children, visitor); + } + + public int index() { + return index; + } + } + + public interface LeafVisitor { + public void visit(LeafNode node); + } +} From b8063285f32237430e8bb18c0fb5f0364d88d615 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 22 Nov 2010 16:46:10 -0700 Subject: [PATCH 7/9] fix deadlock MySystem::visit in posix.cpp We must call notifyAll on visitLock after setting threadVisitor to null in case another thread is waiting to do a visit of its own. Otherwise, the latter thread will wait forever, eventually deadlocking the whole VM at the next GC since it's in an active state. --- src/posix.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/posix.cpp b/src/posix.cpp index e5ec88b0a1..84f3a1fc60 100644 --- a/src/posix.cpp +++ b/src/posix.cpp @@ -655,6 +655,8 @@ class MySystem: public System { threadVisitor = 0; + system->visitLock->notifyAll(t); + return 0; } else { return -1; From 44fcc5c04e9b7ae2186dea7e16a18f0b0d557a7a Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 22 Nov 2010 16:57:02 -0700 Subject: [PATCH 8/9] clean up properly if pthread_kill fails in MySystem::visit --- src/posix.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/posix.cpp b/src/posix.cpp index 84f3a1fc60..1ccf604f68 100644 --- a/src/posix.cpp +++ b/src/posix.cpp @@ -650,17 +650,22 @@ class MySystem: public System { int rv = pthread_kill(target->thread, VisitSignal); + int result; if (rv == 0) { while (visitTarget) visitLock->wait(t, 0); - threadVisitor = 0; - - system->visitLock->notifyAll(t); - - return 0; + result = 0; } else { - return -1; + visitTarget = 0; + + result = -1; } + + threadVisitor = 0; + + system->visitLock->notifyAll(t); + + return result; } virtual uint64_t call(void* function, uintptr_t* arguments, uint8_t* types, From 9a676b4f1f824f3c8f92cdd48a8a074fbc20d201 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 23 Nov 2010 15:54:35 -0700 Subject: [PATCH 9/9] fix OS X 10.4 PowerPC cross-build --- makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/makefile b/makefile index 20cb58e8ae..f4ac4b0849 100644 --- a/makefile +++ b/makefile @@ -117,6 +117,8 @@ common-cflags = $(warnings) -fno-rtti -fno-exceptions -fno-omit-frame-pointer \ build-cflags = $(common-cflags) -fPIC -fvisibility=hidden \ "-I$(JAVA_HOME)/include/linux" -I$(src) -pthread +converter-cflags = -D__STDC_CONSTANT_MACROS + cflags = $(build-cflags) common-lflags = -lm -lz $(gnu-lflags) @@ -172,7 +174,7 @@ ifeq ($(platform),darwin) ifeq ($(arch),powerpc) ifneq (,$(filter i386 x86_64,$(build-arch))) - converter-cflags = -DOPPOSITE_ENDIAN + converter-cflags += -DOPPOSITE_ENDIAN endif cflags += -arch ppc asmflags += -arch ppc @@ -181,7 +183,7 @@ ifeq ($(platform),darwin) ifeq ($(arch),i386) ifeq ($(build-arch),powerpc) - converter-cflags = -DOPPOSITE_ENDIAN + converter-cflags += -DOPPOSITE_ENDIAN endif cflags += -arch i386 asmflags += -arch i386 @@ -190,7 +192,7 @@ ifeq ($(platform),darwin) ifeq ($(arch),x86_64) ifeq ($(build-arch),powerpc) - converter-cflags = -DOPPOSITE_ENDIAN + converter-cflags += -DOPPOSITE_ENDIAN endif cflags += -arch x86_64 asmflags += -arch x86_64