diff --git a/makefile b/makefile
index e85eef1e39..1fbd4b5920 100644
--- a/makefile
+++ b/makefile
@@ -47,7 +47,8 @@ interpreter-depends = \
 	$(src)/vm.h
 interpreter-sources = \
 	$(src)/vm.cpp \
-	$(src)/heap.cpp
+	$(src)/heap.cpp \
+	$(src)/main.cpp
 interpreter-objects = $(call cpp-objects,$(interpreter-sources),$(src))
 interpreter-cflags = $(slow) $(cflags)
 
diff --git a/src/class_finder.h b/src/class_finder.h
index 1c52ee0f23..d46061ab73 100644
--- a/src/class_finder.h
+++ b/src/class_finder.h
@@ -7,9 +7,16 @@ namespace vm {
 
 class ClassFinder {
  public:
+  class Data {
+   public:
+    virtual ~Data() { }
+    virtual const uint8_t* start() = 0;
+    virtual size_t length() = 0;
+    virtual void dispose() = 0;
+  };
+
   virtual ~ClassFinder() { }
-  virtual const uint8_t* find(const char* className, unsigned* size) = 0;
-  virtual void free(const uint8_t* class_) = 0;
+  virtual Data* find(const char* className) = 0;
 };
 
 } // namespace vm
diff --git a/src/heap.cpp b/src/heap.cpp
index 28492d930e..c88b0dbb06 100644
--- a/src/heap.cpp
+++ b/src/heap.cpp
@@ -238,12 +238,8 @@ class Segment {
     map(map)
   {
     if (capacity) {
-      unsigned count = footprint(capacity) * BytesPerWord;
-      data = static_cast<uintptr_t*>(system(context)->allocate(&count));
-
-      if (count != footprint(capacity) * BytesPerWord) {
-        abort(context);
-      }
+      data = static_cast<uintptr_t*>
+        (system(context)->allocate(footprint(capacity) * BytesPerWord));
 
       if (map) {
         map->setSegment(this);
@@ -998,6 +994,8 @@ collect(Context* c)
 
 } // namespace
 
+namespace vm {
+
 Heap*
 makeHeap(System* system)
 {
@@ -1047,11 +1045,7 @@ makeHeap(System* system)
     Context c;
   };
   
-  unsigned count = sizeof(Heap);
-  void* p = system->allocate(&count);
-  if (count != sizeof(Heap)) {
-    system->abort();
-  }
-
-  return new (p) Heap(system);
+  return new (system->allocate(sizeof(Heap))) Heap(system);
 }
+
+} // namespace vm
diff --git a/src/heap.h b/src/heap.h
index f618435268..3203ea4e1a 100644
--- a/src/heap.h
+++ b/src/heap.h
@@ -1,6 +1,8 @@
 #ifndef HEAP_H
 #define HEAP_H
 
+#include "system.h"
+
 namespace vm {
 
 class Heap {
@@ -38,6 +40,8 @@ class Heap {
   virtual void dispose() = 0;
 };
 
+Heap* makeHeap(System* system);
+
 } // namespace vm
 
 #endif//HEAP_H
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000000..8723e0f865
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,244 @@
+#include "sys/mman.h"
+#include "sys/types.h"
+#include "sys/stat.h"
+#include "fcntl.h"
+#include "system.h"
+#include "heap.h"
+#include "vm.h"
+
+using namespace vm;
+
+namespace {
+
+class System: public vm::System {
+ public:
+  System(unsigned limit): limit(limit), count(0) { }
+
+  virtual bool success(Status s) {
+    return s == 0;
+  }
+
+  virtual void* allocate(unsigned* size) {
+    if (count + *size > limit) {
+      *size = limit - count;
+    }
+
+    uintptr_t* up = static_cast<uintptr_t*>(malloc(*size + sizeof(uintptr_t)));
+    if (up == 0) abort();
+
+    *up = *size;
+    return up + 1;
+  }
+
+  virtual void free(const void* p) {
+    if (p) {
+      const uintptr_t* up = static_cast<const uintptr_t*>(p) - 1;
+      count -= *up;
+      ::free(const_cast<uintptr_t*>(up));
+    }
+  }
+
+  virtual Status start(Thread*) {
+    return 1;
+  }
+
+  virtual Status make(Monitor**) {
+    return 1;
+  }
+
+  virtual void abort() {
+    ::abort();
+  }
+
+  unsigned limit;
+  unsigned count;
+};
+
+const char*
+append(vm::System* s, const char* a, const char* b, const char* c)
+{
+  unsigned al = strlen(a);
+  unsigned bl = strlen(b);
+  unsigned cl = strlen(c);
+  char* p = static_cast<char*>(s->allocate(al + bl + cl + 1));
+  memcpy(p, a, al);
+  memcpy(p + al, b, bl);
+  memcpy(p + al + bl, c, cl + 1);
+  return p;
+}
+
+class ClassFinder: public vm::ClassFinder {
+ public:
+  ClassFinder(vm::System* system, const char** path):
+    system(system),
+    path(path)
+  { }
+
+  class Data: public vm::ClassFinder::Data {
+   public:
+    Data(uint8_t* start, size_t length):
+      start_(start),
+      length_(length)
+    { }
+
+    virtual const uint8_t* start() {
+      return start_;
+    }
+
+    virtual size_t length() {
+      return length_;
+    }
+
+    virtual void dispose() {
+      if (start_) {
+        munmap(start_, length_);
+      }
+    }
+
+    uint8_t* start_;
+    size_t length_;
+  };
+
+  virtual Data* find(const char* className) {
+    Data* d = new (system->allocate(sizeof(Data))) Data(0, 0);
+
+    for (const char** p = path; *p; ++p) {
+      const char* file = append(system, *p, "/", className);
+      int fd = open(file, O_RDONLY);
+      if (fd != -1) {
+        struct stat s;
+        int r = fstat(fd, &s);
+        if (r != -1) {
+          void* data = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+          if (data) {
+            d->start_ = static_cast<uint8_t*>(data);
+            d->length_ = s.st_size;
+            return d;
+          }
+        }
+      }
+      system->free(file);
+    }
+    
+    system->free(d);
+    return 0;
+  }
+
+  vm::System* system;
+  const char** path;
+};
+
+const char**
+parsePath(vm::System* s, const char* path)
+{
+  class Tokenizer {
+   public:
+    class Token {
+     public:
+      Token(const char* s, unsigned length): s(s), length(length) { }
+
+      const char* s;
+      unsigned length;
+    };
+
+    Tokenizer(const char* s, char delimiter): s(s), delimiter(delimiter) { }
+
+    bool hasMore() {
+      while (*s == delimiter) ++s;
+      return *s;
+    }
+
+    Token next() {
+      const char* p = s;
+      while (*s and *s != delimiter) ++s;
+      return Token(p, s - p);
+    }
+
+    const char* s;
+    char delimiter;
+  };
+
+  unsigned count = 0;
+  for (Tokenizer t(path, ':'); t.hasMore();) ++ count;
+
+  const char** v = static_cast<const char**>
+    (s->allocate((count + 1) * sizeof(const char*)));
+
+  unsigned i = 0;
+  for (Tokenizer t(path, ':'); t.hasMore(); ++i) {
+    Tokenizer::Token token(t.next());
+    char* p = static_cast<char*>(s->allocate(token.length));
+    memcpy(p, token.s, token.length);
+    p[token.length] = 0;
+    v[i] = p;
+  }
+
+  v[i] = 0;
+
+  return v;
+}
+
+void
+run(unsigned heapSize, const char* path, const char* class_, int argc,
+    const char** argv)
+{
+  System s(heapSize);
+
+  const char** pathv = parsePath(&s, path);
+  ClassFinder cf(&s, pathv);
+
+  Heap* heap = makeHeap(&s);
+
+  run(&s, heap, &cf, class_, argc, argv);
+
+  heap->dispose();
+
+  for (const char** p = pathv; *p; ++p) {
+    s.free(*p);
+  }
+
+  s.free(pathv);
+}
+
+void
+usageAndExit(const char* name)
+{
+  fprintf(stderr, "usage: %s [-cp <classpath>] [-hs <maximum heap size>] "
+          "<class name> [<argument> ...]", name);
+  exit(-1);
+}
+
+} // namespace
+
+int
+main(int ac, const char** av)
+{
+  unsigned heapSize = 4 * 1024 * 1024;
+  const char* path = ".";
+  const char* class_ = 0;
+  int argc = 0;
+  const char** argv = 0;
+
+  for (int i = 1; i < ac; ++i) {
+    if (strcmp(av[i], "-cp") == 0) {
+      path = av[++i];
+    } else if (strcmp(av[i], "-hs") == 0) {
+      heapSize = atoi(av[++i]);
+    } else {
+      class_ = av[i++];
+      if (i < ac) {
+        argc = ac - i;
+        argv = av + i;
+        i = ac;
+      }
+    }
+  }
+
+  if (class_ == 0) {
+    usageAndExit(av[0]);
+  }
+
+  run(heapSize, path, class_, argc, argv);
+
+  return 0;
+}
diff --git a/src/system.h b/src/system.h
index d8214ed493..0ab5258952 100644
--- a/src/system.h
+++ b/src/system.h
@@ -27,39 +27,23 @@ class System {
     virtual void dispose() = 0;
   };
 
-  class File {
-   public:
-    virtual ~File() { }
-    virtual Status read(uint8_t* data, unsigned* size) = 0;
-    virtual Status write(const uint8_t* data, unsigned size) = 0;
-    virtual Status close() = 0;
-  };
-
-  static const int ReadOnly = 00;
-  static const int WriteOnly = 01;
-  static const int ReadWrite = 02;
-  static const int Append = 02000;
-  static const int Create = 0100;
-
-  static const int UserRead = 0400;
-  static const int UserWrite = 0200;
-  static const int UserExecute = 0100;
-  static const int GroupRead = 040;
-  static const int GroupWrite = 020;
-  static const int GroupExecute = 010;
-  static const int OtherRead = 04;
-  static const int OtherWrite = 02;
-  static const int OtherExecute = 01;
-
   virtual ~System() { }
 
   virtual bool success(Status) = 0;
   virtual void* allocate(unsigned* size) = 0;
-  virtual void free(void*) = 0;
+  virtual void free(const void*) = 0;
   virtual Status start(Thread*) = 0;
   virtual Status make(Monitor**) = 0;
-  virtual Status open(File**, const char* path, int flags, int mode) = 0;
   virtual void abort() = 0;
+
+  void* allocate(unsigned size) {
+    unsigned requested = size;
+    void* p = allocate(&size);
+    if (size != requested) {
+      abort();
+    }
+    return p;
+  }
 };
 
 } // namespace vm
diff --git a/src/vm.cpp b/src/vm.cpp
index 97caa24799..3785d7e897 100644
--- a/src/vm.cpp
+++ b/src/vm.cpp
@@ -1429,15 +1429,13 @@ resolveClass(Thread* t, object spec)
   object class_ = hashMapFind
     (t, t->vm->classMap, spec, byteArrayHash, byteArrayEqual);
   if (class_ == 0) {
-    unsigned size;
-    const uint8_t* data = t->vm->classFinder->find
-      (reinterpret_cast<const char*>(&byteArrayBody(t, spec, 0)), &size);
+    ClassFinder::Data* data = t->vm->classFinder->find
+      (reinterpret_cast<const char*>(&byteArrayBody(t, spec, 0)));
 
     if (data) {
       // parse class file
-      class_ = parseClass(t, data, size);
-
-      t->vm->classFinder->free(data);
+      class_ = parseClass(t, data->start(), data->length());
+      data->dispose();
 
       PROTECT(t, class_);