diff --git a/classpath/java-io.cpp b/classpath/java-io.cpp
new file mode 100644
index 0000000000..00d3a66979
--- /dev/null
+++ b/classpath/java-io.cpp
@@ -0,0 +1,261 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "jni.h"
+
+#undef JNIEXPORT
+#define JNIEXPORT __attribute__ ((visibility("default")))
+
+#ifdef WIN32
+#  include <io.h>
+#  define OPEN _open
+#  define CLOSE _close
+#  define READ _read
+#  define WRITE _write
+#  define STAT _stat
+#  define STRUCT_STAT struct _stat
+#  define MKDIR(path, mode) _mkdir(path)
+#  define CREAT _creat
+#  define OPEN_MASK O_BINARY
+#else
+#  include <unistd.h>
+#  define OPEN open
+#  define CLOSE close
+#  define READ read
+#  define WRITE write
+#  define STAT stat
+#  define STRUCT_STAT struct stat
+#  define MKDIR mkdir
+#  define CREAT creat
+#  define OPEN_MASK 0
+#endif
+
+namespace {
+
+inline void
+throwNew(JNIEnv* e, const char* class_, const char* message)
+{
+  jclass c = e->FindClass(class_);
+  if (c) {
+    e->ThrowNew(c, message);
+    e->DeleteLocalRef(c);
+  }
+}
+
+inline bool
+exists(const char* path)
+{
+  STRUCT_STAT s;
+  return STAT(path, &s) == 0;
+}
+
+inline int
+doOpen(JNIEnv* e, const char* path, int mask)
+{
+  int fd = OPEN(path, mask | OPEN_MASK, S_IRUSR | S_IWUSR);
+  if (fd == -1) {
+    throwNew(e, "java/lang/IOException", strerror(errno));
+  }
+  return fd;
+}
+
+inline void
+doClose(JNIEnv* e, jint fd)
+{
+  int r = CLOSE(fd);
+  if (r == -1) {
+    throwNew(e, "java/lang/IOException", strerror(errno));
+  }
+}
+
+inline int
+doRead(JNIEnv* e, jint fd, jbyte* data, jint length)
+{
+  int r = READ(fd, data, length);
+  if (r > 0) {
+    return r;
+  } else if (r == 0) {
+    return -1;
+  } else {
+    throwNew(e, "java/lang/IOException", strerror(errno));
+    return 0;
+  }  
+}
+
+inline void
+doWrite(JNIEnv* e, jint fd, const jbyte* data, jint length)
+{
+  int r = WRITE(fd, data, length);
+  if (r != length) {
+    throwNew(e, "java/lang/IOException", strerror(errno));
+  }  
+}
+
+} // namespace
+
+extern "C" JNIEXPORT jstring JNICALL
+Java_java_io_File_toAbsolutePath(JNIEnv* /*e*/, jclass, jstring path)
+{
+  // todo
+  return path;
+}
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_java_io_File_length(JNIEnv* e, jclass, jstring path)
+{
+  const char* chars = e->GetStringUTFChars(path, 0);
+  if (chars) {
+    STRUCT_STAT s;
+    int r = STAT(chars, &s);
+    if (r == 0) {
+      return s.st_size;
+    }
+    e->ReleaseStringUTFChars(path, chars);
+  }
+
+  return -1;
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_java_io_File_mkdir(JNIEnv* e, jclass, jstring path)
+{
+  const char* chars = e->GetStringUTFChars(path, 0);
+  if (chars) {
+    if (not exists(chars)) {
+      int r = ::MKDIR(chars, 0700);
+      if (r != 0) {
+        throwNew(e, "java/lang/IOException", strerror(errno));
+      }
+    }
+    e->ReleaseStringUTFChars(path, chars);
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_java_io_File_createNewFile(JNIEnv* e, jclass, jstring path)
+{
+  const char* chars = e->GetStringUTFChars(path, 0);
+  if (chars) {
+    if (not exists(chars)) {
+      int fd = CREAT(chars, 0600);
+      if (fd == -1) {
+        throwNew(e, "java/lang/IOException", strerror(errno));
+      } else {
+        doClose(e, fd);
+      }
+    }
+    e->ReleaseStringUTFChars(path, chars);
+  }
+}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_java_io_File_exists(JNIEnv* e, jclass, jstring path)
+{
+  const char* chars = e->GetStringUTFChars(path, 0);
+  if (chars) {
+    bool v = exists(chars);
+    e->ReleaseStringUTFChars(path, chars);
+    return v;
+  } else {
+    return false;
+  }
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_java_io_FileInputStream_open(JNIEnv* e, jclass, jstring path)
+{
+  const char* chars = e->GetStringUTFChars(path, 0);
+  if (chars) {
+    int fd = doOpen(e, chars, O_RDONLY);
+    e->ReleaseStringUTFChars(path, chars);
+    return fd;
+  } else {
+    return -1;
+  }
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_java_io_FileInputStream_read__I(JNIEnv* e, jclass, jint fd)
+{
+  jbyte data;
+  int r = doRead(e, fd, &data, 1);
+  if (r <= 0) {
+    return -1;
+  } else {
+    return data;
+  }
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_java_io_FileInputStream_read__I_3BII
+(JNIEnv* e, jclass, jint fd, jbyteArray b, jint offset, jint length)
+{
+  jbyte* data = static_cast<jbyte*>(malloc(length));
+  if (data == 0) {
+    throwNew(e, "java/lang/OutOfMemoryError", 0);
+    return 0;    
+  }
+
+  int r = doRead(e, fd, data, length);
+
+  e->SetByteArrayRegion(b, offset, length, data);
+
+  free(data);
+
+  return r;
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_java_io_FileInputStream_close(JNIEnv* e, jclass, jint fd)
+{
+  doClose(e, fd);
+}
+
+extern "C" JNIEXPORT jint JNICALL
+Java_java_io_FileOutputStream_open(JNIEnv* e, jclass, jstring path)
+{
+  const char* chars = e->GetStringUTFChars(path, 0);
+  if (chars) {
+    int fd = doOpen(e, chars, O_WRONLY | O_CREAT);
+    e->ReleaseStringUTFChars(path, chars);
+    return fd;
+  } else {
+    return -1;
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_java_io_FileOutputStream_write__II(JNIEnv* e, jclass, jint fd, jint c)
+{
+  jbyte data = c;
+  doWrite(e, fd, &data, 1);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_java_io_FileOutputStream_write__I_3BII
+(JNIEnv* e, jclass, jint fd, jbyteArray b, jint offset, jint length)
+{
+  jbyte* data = static_cast<jbyte*>(malloc(length));
+  if (data == 0) {
+    throwNew(e, "java/lang/OutOfMemoryError", 0);
+    return;    
+  }
+
+  e->GetByteArrayRegion(b, offset, length, data);
+
+  if (not e->ExceptionCheck()) {
+    doWrite(e, fd, data, length);
+  }
+
+  free(data);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_java_io_FileOutputStream_close(JNIEnv* e, jclass, jint fd)
+{
+  doClose(e, fd);
+}
diff --git a/classpath/java/lang/System.cpp b/classpath/java-lang.cpp
similarity index 75%
rename from classpath/java/lang/System.cpp
rename to classpath/java-lang.cpp
index 7cb7402375..b8c961c4e4 100644
--- a/classpath/java/lang/System.cpp
+++ b/classpath/java-lang.cpp
@@ -9,14 +9,13 @@ Java_java_lang_System_getProperty(JNIEnv* e, jclass, jstring key)
 {
   jstring value = 0;
 
-  jboolean isCopy;
-  const char* chars = e->GetStringUTFChars(key, &isCopy);
+  const char* chars = e->GetStringUTFChars(key, 0);
   if (chars) {
     if (strcmp(chars, "line.separator") == 0) {
       value = e->NewStringUTF("\n");
     }
+    e->ReleaseStringUTFChars(key, chars);
   }
-  e->ReleaseStringUTFChars(key, chars);
 
   return value;
 }
diff --git a/classpath/java/io/File.java b/classpath/java/io/File.java
new file mode 100644
index 0000000000..1f6bbb51b0
--- /dev/null
+++ b/classpath/java/io/File.java
@@ -0,0 +1,61 @@
+package java.io;
+
+public class File {
+  private final String path;
+
+  public File(String path) {
+    if (path == null) throw new NullPointerException();
+    this.path = path;
+  }
+
+  public File(String parent, String child) {
+    this(parent + "/" + child);
+  }
+
+  public File(File parent, String child) {
+    this(parent.getPath() + "/" + child);
+  }
+
+  public String getName() {
+    int index = path.lastIndexOf("/");
+    if (index >= 0) {
+      return path.substring(index + 1);
+    } else {
+      return path;
+    }
+  }
+
+  public String getPath() {
+    return path;
+  }
+
+  private static native String toAbsolutePath(String path);
+
+  public String getAbsolutePath() {
+    return toAbsolutePath(path);
+  }
+
+  private static native long length(String path);
+
+  public long length() {
+    return length(path);
+  }
+
+  private static native boolean exists(String path);
+
+  public boolean exists() {
+    return exists(path);
+  }
+
+  private static native void mkdir(String path);
+
+  public void mkdir() {
+    mkdir(path);
+  }
+
+  private static native void createNewFile(String path);
+
+  public void createNewFile() {
+    createNewFile(path);
+  }
+}
diff --git a/classpath/java/io/FileInputStream.cpp b/classpath/java/io/FileInputStream.cpp
deleted file mode 100644
index 0da02c68f1..0000000000
--- a/classpath/java/io/FileInputStream.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "jni.h"
-
-#undef JNIEXPORT
-#define JNIEXPORT __attribute__ ((visibility("default")))
-
-#ifdef WIN32
-#  include <io.h>
-#  define CLOSE _close
-#  define READ _read
-#else
-#  include <unistd.h>
-#  define CLOSE close
-#  define READ read
-#endif
-
-namespace {
-
-int
-doRead(JNIEnv* e, jint fd, jbyte* data, jint length)
-{
-  int r = READ(fd, data, length);
-  if (r > 0) {
-    return r;
-  } else if (r == 0) {
-    return -1;
-  } else {
-    e->ThrowNew(e->FindClass("java/lang/IOException"), strerror(errno));
-    return 0;
-  }  
-}
-
-} // namespace
-
-extern "C" JNIEXPORT jint JNICALL
-Java_java_io_FileInputStream_read__I(JNIEnv* e, jclass, jint fd)
-{
-  jbyte data;
-  int r = doRead(e, fd, &data, 1);
-  if (r <= 0) {
-    return -1;
-  } else {
-    return data;
-  }
-}
-
-extern "C" JNIEXPORT jint JNICALL
-Java_java_io_FileInputStream_read__I_3BII
-(JNIEnv* e, jclass, jint fd, jbyteArray b, jint offset, jint length)
-{
-  jbyte* data = static_cast<jbyte*>(malloc(length));
-  if (data == 0) {
-    e->ThrowNew(e->FindClass("java/lang/OutOfMemoryError"), 0);
-    return 0;    
-  }
-
-  int r = doRead(e, fd, data, length);
-
-  e->SetByteArrayRegion(b, offset, length, data);
-
-  free(data);
-
-  return r;
-}
-
-extern "C" JNIEXPORT void JNICALL
-Java_java_io_FileInputStream_close(JNIEnv* e, jclass, jint fd)
-{
-  int r = CLOSE(fd);
-  if (r == -1) {
-    e->ThrowNew(e->FindClass("java/lang/IOException"), strerror(errno));
-  }
-}
diff --git a/classpath/java/io/FileInputStream.java b/classpath/java/io/FileInputStream.java
index d01f77325f..992d959ca7 100644
--- a/classpath/java/io/FileInputStream.java
+++ b/classpath/java/io/FileInputStream.java
@@ -7,6 +7,16 @@ public class FileInputStream extends InputStream {
     this.fd = fd.value;
   }
 
+  public FileInputStream(String path) throws IOException {
+    fd = open(path);
+  }
+
+  public FileInputStream(File file) throws IOException {
+    this(file.getPath());
+  }
+
+  private static native int open(String path) throws IOException;
+
   private static native int read(int fd) throws IOException;
 
   private static native int read(int fd, byte[] b, int offset, int length)
@@ -19,6 +29,14 @@ public class FileInputStream extends InputStream {
   }
 
   public int read(byte[] b, int offset, int length) throws IOException {
+    if (b == null) {
+      throw new NullPointerException();
+    }
+
+    if (offset < 0 || offset + length > b.length) {
+      throw new ArrayIndexOutOfBoundsException();
+    }
+
     return read(fd, b, offset, length);
   }
 
diff --git a/classpath/java/io/FileOutputStream.cpp b/classpath/java/io/FileOutputStream.cpp
deleted file mode 100644
index 3fcc3d47a6..0000000000
--- a/classpath/java/io/FileOutputStream.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "jni.h"
-
-#undef JNIEXPORT
-#define JNIEXPORT __attribute__ ((visibility("default")))
-
-#ifdef WIN32
-#  include <io.h>
-#  define CLOSE _close
-#  define WRITE _write
-#else
-#  include <unistd.h>
-#  define CLOSE close
-#  define WRITE write
-#endif
-
-namespace {
-
-void
-doWrite(JNIEnv* e, jint fd, const jbyte* data, jint length)
-{
-  int r = WRITE(fd, data, length);
-  if (r != length) {
-    e->ThrowNew(e->FindClass("java/lang/IOException"), strerror(errno));
-  }  
-}
-
-} // namespace
-
-extern "C" JNIEXPORT void JNICALL
-Java_java_io_FileOutputStream_write__II(JNIEnv* e, jclass, jint fd, jint c)
-{
-  jbyte data = c;
-  doWrite(e, fd, &data, 1);
-}
-
-extern "C" JNIEXPORT void JNICALL
-Java_java_io_FileOutputStream_write__I_3BII
-(JNIEnv* e, jclass, jint fd, jbyteArray b, jint offset, jint length)
-{
-  jbyte* data = static_cast<jbyte*>(malloc(length));
-  if (data == 0) {
-    e->ThrowNew(e->FindClass("java/lang/OutOfMemoryError"), 0);
-    return;    
-  }
-
-  e->GetByteArrayRegion(b, offset, length, data);
-
-  if (not e->ExceptionCheck()) {
-    doWrite(e, fd, data, length);
-  }
-
-  free(data);
-}
-
-extern "C" JNIEXPORT void JNICALL
-Java_java_io_FileOutputStream_close(JNIEnv* e, jclass, jint fd)
-{
-  int r = CLOSE(fd);
-  if (r == -1) {
-    e->ThrowNew(e->FindClass("java/lang/IOException"), strerror(errno));
-  }
-}
diff --git a/classpath/java/io/FileOutputStream.java b/classpath/java/io/FileOutputStream.java
index e6b99a3222..e2dda678da 100644
--- a/classpath/java/io/FileOutputStream.java
+++ b/classpath/java/io/FileOutputStream.java
@@ -7,6 +7,16 @@ public class FileOutputStream extends OutputStream {
     this.fd = fd.value;
   }
 
+  public FileOutputStream(String path) throws IOException {
+    fd = open(path);
+  }
+
+  public FileOutputStream(File file) throws IOException {
+    this(file.getPath());
+  }
+
+  private static native int open(String path) throws IOException;
+
   public static native void write(int fd, int c) throws IOException;
 
   public static native void write(int fd, byte[] b, int offset, int length)
@@ -19,6 +29,14 @@ public class FileOutputStream extends OutputStream {
   }
 
   public void write(byte[] b, int offset, int length) throws IOException {
+    if (b == null) {
+      throw new NullPointerException();
+    }
+
+    if (offset < 0 || offset + length > b.length) {
+      throw new ArrayIndexOutOfBoundsException();
+    }
+
     write(fd, b, offset, length);
   }
 
diff --git a/classpath/java/lang/String.java b/classpath/java/lang/String.java
index bac9087698..482de19b1c 100644
--- a/classpath/java/lang/String.java
+++ b/classpath/java/lang/String.java
@@ -87,6 +87,42 @@ public final class String implements Comparable<String> {
     }
   }
 
+  public int indexOf(String s) {
+    if (s.length == 0) return 0;
+
+    for (int i = 0; i < length - s.length; ++i) {
+      int j = 0;
+      for (; j < s.length; ++j) {
+        if (charAt(i + j) != s.charAt(j)) {
+          break;
+        }
+      }
+      if (j == s.length) {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+  public int lastIndexOf(String s) {
+    if (s.length == 0) return length;
+
+    for (int i = length - s.length; i >= 0; --i) {
+      int j = 0;
+      for (; j < s.length && i + j < length; ++j) {
+        if (charAt(i + j) != s.charAt(j)) {
+          break;
+        }
+      }
+      if (j == s.length) {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
   public String substring(int start) {
     return substring(start, length);
   }
diff --git a/makefile b/makefile
index ca9db33a67..c29bec3b08 100644
--- a/makefile
+++ b/makefile
@@ -16,7 +16,7 @@ src = src
 classpath = classpath
 test = test
 
-input = $(cls)/Hello.class
+input = $(cls)/Reflection.class
 
 cxx = g++
 cc = gcc
diff --git a/src/builtin.cpp b/src/builtin.cpp
index 31b93b87af..12991e84a0 100644
--- a/src/builtin.cpp
+++ b/src/builtin.cpp
@@ -23,14 +23,8 @@ replace(char a, char b, char* c)
   for (; *c; ++c) if (*c == a) *c = b;
 }
 
-} // namespace
-
-namespace vm {
-
-namespace builtin {
-
 jstring
-toString(Thread* t, jobject this_)
+Object_toString(Thread* t, jobject this_)
 {
   object s = makeString
     (t, "%s@%p",
@@ -41,31 +35,31 @@ toString(Thread* t, jobject this_)
 }
 
 jclass
-getClass(Thread* t, jobject this_)
+Object_getClass(Thread* t, jobject this_)
 {
   return pushReference(t, objectClass(t, *this_));
 }
 
 void
-wait(Thread* t, jobject this_, jlong milliseconds)
+Object_wait(Thread* t, jobject this_, jlong milliseconds)
 {
   vm::wait(t, *this_, milliseconds);
 }
 
 void
-notify(Thread* t, jobject this_)
+Object_notify(Thread* t, jobject this_)
 {
   vm::notify(t, *this_);
 }
 
 void
-notifyAll(Thread* t, jobject this_)
+Object_notifyAll(Thread* t, jobject this_)
 {
   vm::notifyAll(t, *this_);
 }
 
 jclass
-forName(Thread* t, jclass, jstring name)
+Class_forName(Thread* t, jclass, jstring name)
 {
   if (LIKELY(name)) {
     object n = makeByteArray(t, stringLength(t, *name) + 1, false);
@@ -95,7 +89,7 @@ forName(Thread* t, jclass, jstring name)
 }
 
 jboolean
-isAssignableFrom(Thread* t, jobject this_, jclass that)
+Class_isAssignableFrom(Thread* t, jobject this_, jclass that)
 {
   if (LIKELY(that)) {
     return vm::isAssignableFrom(t, *this_, *that);
@@ -106,7 +100,7 @@ isAssignableFrom(Thread* t, jobject this_, jclass that)
 }
 
 jobject
-get(Thread* t, jobject this_, jobject instancep)
+Field_get(Thread* t, jobject this_, jobject instancep)
 {
   object field = *this_;
 
@@ -169,7 +163,8 @@ get(Thread* t, jobject this_, jobject instancep)
 }
 
 jobject
-invoke(Thread* t, jobject this_, jobject instancep, jobjectArray argumentsp)
+Method_invoke(Thread* t, jobject this_, jobject instancep,
+              jobjectArray argumentsp)
 {
   object method = *this_;
 
@@ -206,25 +201,9 @@ invoke(Thread* t, jobject this_, jobject instancep, jobjectArray argumentsp)
   return 0;
 }
 
-jobject
-currentThread(Thread* t, jclass)
-{
-  return pushReference(t, t->javaThread);
-}
-
 void
-sleep(Thread* t, jclass, jlong milliseconds)
-{
-  if (milliseconds == 0) milliseconds = INT64_MAX;
-
-  ENTER(t, Thread::IdleState);
-
-  t->vm->system->sleep(milliseconds);
-}
-
-void
-arraycopy(Thread* t, jclass, jobject src, jint srcOffset, jobject dst,
-          jint dstOffset, jint length)
+System_arraycopy(Thread* t, jclass, jobject src, jint srcOffset, jobject dst,
+                 jint dstOffset, jint length)
 {
   if (LIKELY(src and dst)) {
     object s = *src;
@@ -263,13 +242,13 @@ arraycopy(Thread* t, jclass, jobject src, jint srcOffset, jobject dst,
 }
 
 jlong
-currentTimeMillis(Thread* t, jclass)
+System_currentTimeMillis(Thread* t, jclass)
 {
   return t->vm->system->now();
 }
 
 void
-loadLibrary(Thread* t, jobject, jstring name)
+Runtime_loadLibrary(Thread* t, jobject, jstring name)
 {
   if (LIKELY(name)) {
     char n[stringLength(t, *name) + 1];
@@ -297,7 +276,7 @@ loadLibrary(Thread* t, jobject, jstring name)
 }
 
 void
-gc(Thread* t, jobject)
+Runtime_gc(Thread* t, jobject)
 {
   ENTER(t, Thread::ExclusiveState);
 
@@ -305,13 +284,13 @@ gc(Thread* t, jobject)
 }
 
 void
-exit(Thread* t, jobject, jint code)
+Runtime_exit(Thread* t, jobject, jint code)
 {
   t->vm->system->exit(code);
 }
 
 jobject
-trace(Thread* t, jclass, jint skipCount)
+Throwable_trace(Thread* t, jclass, jint skipCount)
 {
   int frame = t->frame;
   while (skipCount-- and frame >= 0) {
@@ -334,7 +313,7 @@ trace(Thread* t, jclass, jint skipCount)
 }
 
 jarray
-resolveTrace(Thread* t, jclass, jobject trace)
+Throwable_resolveTrace(Thread* t, jclass, jobject trace)
 {
   unsigned length = arrayLength(t, *trace);
   object array = makeObjectArray
@@ -367,8 +346,23 @@ resolveTrace(Thread* t, jclass, jobject trace)
   return pushReference(t, array);
 }
 
+jobject
+Thread_currentThread(Thread* t, jclass)
+{
+  return pushReference(t, t->javaThread);
+}
+
 void
-start(Thread* t, jobject this_)
+Thread_sleep(Thread* t, jclass, jlong milliseconds)
+{
+  if (milliseconds == 0) milliseconds = INT64_MAX;
+
+  ENTER(t, Thread::IdleState);
+
+  t->vm->system->sleep(milliseconds);
+}
+void
+Thread_start(Thread* t, jobject this_)
 {
   Thread* p = reinterpret_cast<Thread*>(threadPeer(t, *this_));
   if (p) {
@@ -413,56 +407,62 @@ start(Thread* t, jobject this_)
   }
 }
 
+} // namespace
+
+namespace vm {
+
 void
-populate(Thread* t, object map)
+populateBuiltinMap(Thread* t, object map)
 {
   struct {
     const char* key;
     void* value;
   } builtins[] = {
     { "Java_java_lang_Class_forName",
-      reinterpret_cast<void*>(forName) },
+      reinterpret_cast<void*>(::Class_forName) },
     { "Java_java_lang_Class_isAssignableFrom",
-      reinterpret_cast<void*>(isAssignableFrom) },
+      reinterpret_cast<void*>(::Class_isAssignableFrom) },
 
     { "Java_java_lang_System_arraycopy",
-      reinterpret_cast<void*>(arraycopy) },
+      reinterpret_cast<void*>(::System_arraycopy) },
+    { "Java_java_lang_System_currentTimeMillis",
+      reinterpret_cast<void*>(::System_currentTimeMillis) },
 
     { "Java_java_lang_Runtime_loadLibrary",
-      reinterpret_cast<void*>(loadLibrary) },
+      reinterpret_cast<void*>(::Runtime_loadLibrary) },
     { "Java_java_lang_Runtime_gc",
-      reinterpret_cast<void*>(gc) },
+      reinterpret_cast<void*>(::Runtime_gc) },
     { "Java_java_lang_Runtiime_exit",
-      reinterpret_cast<void*>(exit) },
+      reinterpret_cast<void*>(::Runtime_exit) },
 
     { "Java_java_lang_Thread_doStart",
-      reinterpret_cast<void*>(start) },
+      reinterpret_cast<void*>(::Thread_start) },
     { "Java_java_lang_Thread_currentThread",
-      reinterpret_cast<void*>(currentThread) },
+      reinterpret_cast<void*>(::Thread_currentThread) },
     { "Java_java_lang_Thread_sleep",
-      reinterpret_cast<void*>(sleep) },
+      reinterpret_cast<void*>(::Thread_sleep) },
 
     { "Java_java_lang_Throwable_resolveTrace",
-      reinterpret_cast<void*>(resolveTrace) },
+      reinterpret_cast<void*>(::Throwable_resolveTrace) },
     { "Java_java_lang_Throwable_trace",
-      reinterpret_cast<void*>(trace) },
+      reinterpret_cast<void*>(::Throwable_trace) },
 
     { "Java_java_lang_Object_getClass",
-      reinterpret_cast<void*>(getClass) },
+      reinterpret_cast<void*>(::Object_getClass) },
     { "Java_java_lang_Object_notify",
-      reinterpret_cast<void*>(notify) },
+      reinterpret_cast<void*>(::Object_notify) },
     { "Java_java_lang_Object_notifyAll",
-      reinterpret_cast<void*>(notifyAll) },
+      reinterpret_cast<void*>(::Object_notifyAll) },
     { "Java_java_lang_Object_toString",
-      reinterpret_cast<void*>(toString) },
+      reinterpret_cast<void*>(::Object_toString) },
     { "Java_java_lang_Object_wait",
-      reinterpret_cast<void*>(wait) },
+      reinterpret_cast<void*>(::Object_wait) },
 
     { "Java_java_lang_reflect_Field_get",
-      reinterpret_cast<void*>(get) },
+      reinterpret_cast<void*>(::Field_get) },
 
     { "Java_java_lang_reflect_Method_invoke",
-      reinterpret_cast<void*>(invoke) },
+      reinterpret_cast<void*>(::Method_invoke) },
 
     { 0, 0 }
   };
@@ -476,6 +476,4 @@ populate(Thread* t, object map)
   }
 }
 
-} // namespace builtin
-
 } // namespace vm
diff --git a/src/builtin.h b/src/builtin.h
index 3c03ffba8c..ae2bb1dd9c 100644
--- a/src/builtin.h
+++ b/src/builtin.h
@@ -5,12 +5,8 @@
 
 namespace vm {
 
-namespace builtin {
-
 void
-populate(Thread* t, object map);
-
-} // namespace builtin
+populateBuiltinMap(Thread* t, object map);
 
 } // namespace vm
 
diff --git a/src/jnienv.cpp b/src/jnienv.cpp
index c373b9b03f..8fee8bac87 100644
--- a/src/jnienv.cpp
+++ b/src/jnienv.cpp
@@ -1,9 +1,9 @@
 #include "jnienv.h"
 #include "machine.h"
 
-namespace vm {
+using namespace vm;
 
-namespace jni {
+namespace {
 
 jsize
 GetStringUTFLength(Thread* t, jstring s)
@@ -41,16 +41,84 @@ NewStringUTF(Thread* t, const char* chars)
 }
 
 void
-populate(JNIEnvVTable* table)
+GetByteArrayRegion(Thread* t, jbyteArray array, jint offset, jint length,
+                   jbyte* dst)
+{
+  ENTER(t, Thread::ActiveState);
+
+  memcpy(dst, &byteArrayBody(t, *array, offset), length);
+}
+
+void
+SetByteArrayRegion(Thread* t, jbyteArray array, jint offset, jint length,
+                   const jbyte* src)
+{
+  ENTER(t, Thread::ActiveState);
+
+  memcpy(&byteArrayBody(t, *array, offset), src, length);
+}
+
+jclass
+FindClass(Thread* t, const char* name)
+{
+  ENTER(t, Thread::ActiveState);
+
+  object n = makeByteArray(t, strlen(name) + 1, false);
+  memcpy(&byteArrayBody(t, n, 0), name, byteArrayLength(t, n));
+
+  return pushReference(t, resolveClass(t, n));
+}
+
+jint
+ThrowNew(Thread* t, jclass c, const char* message)
+{
+  if (t->exception) {
+    return -1;
+  }
+
+  ENTER(t, Thread::ActiveState);
+  
+  object m = 0;
+  PROTECT(t, m);
+
+  if (message) {
+    m = makeString(t, "%s", message);
+  }
+
+  object trace = makeTrace(t);
+  PROTECT(t, trace);
+
+  t->exception = make(t, *c);
+  set(t, throwableMessageUnsafe(t, t->exception), m);
+  set(t, throwableTraceUnsafe(t, t->exception), trace);
+
+  return 0;
+}
+
+jboolean
+ExceptionCheck(Thread* t)
+{
+  return t->exception != 0;
+}
+
+} // namespace
+
+namespace vm {
+
+void
+populateJNITable(JNIEnvVTable* table)
 {
   memset(table, 0, sizeof(JNIEnvVTable));
 
-  table->GetStringUTFLength = GetStringUTFLength;
-  table->GetStringUTFChars = GetStringUTFChars;
-  table->ReleaseStringUTFChars = ReleaseStringUTFChars;
-  table->NewStringUTF = NewStringUTF;
+  table->GetStringUTFLength = ::GetStringUTFLength;
+  table->GetStringUTFChars = ::GetStringUTFChars;
+  table->ReleaseStringUTFChars = ::ReleaseStringUTFChars;
+  table->NewStringUTF = ::NewStringUTF;
+  table->GetByteArrayRegion = ::GetByteArrayRegion;
+  table->SetByteArrayRegion = ::SetByteArrayRegion;
+  table->FindClass = ::FindClass;
+  table->ThrowNew = ::ThrowNew;
+  table->ExceptionCheck = ::ExceptionCheck;
 }
 
-} // namespace jni
-
 } // namespace vm
diff --git a/src/jnienv.h b/src/jnienv.h
index c9454e1598..98b6b461fb 100644
--- a/src/jnienv.h
+++ b/src/jnienv.h
@@ -5,12 +5,8 @@
 
 namespace vm {
 
-namespace jni {
-
 void
-populate(JNIEnvVTable* table);
-
-} // namespace jni
+populateJNITable(JNIEnvVTable* table);
 
 } // namespace vm
 
diff --git a/src/machine.cpp b/src/machine.cpp
index 0b73a80a57..99a2ccc040 100644
--- a/src/machine.cpp
+++ b/src/machine.cpp
@@ -468,7 +468,7 @@ makeJNIName(Thread* t, object method, bool decorate)
     for (unsigned i = 1; i < byteArrayLength(t, methodSpec) - 1
            and byteArrayBody(t, methodSpec, i) != ')'; ++i)
     {
-      index += mangle(byteArrayBody(t, className, i),
+      index += mangle(byteArrayBody(t, methodSpec, i),
                       &byteArrayBody(t, name, index));
     }
   }
@@ -962,12 +962,12 @@ parseMethodTable(Thread* t, Stream& s, object class_, object pool)
 
       if (flags & ACC_NATIVE) {
         object p = hashMapFindNode
-          (t, nativeMap, method, methodHash, methodEqual);
+          (t, nativeMap, methodName(t, method), byteArrayHash, byteArrayEqual);
         
         if (p) {
           set(t, tripleSecond(t, p), method);          
         } else {
-          hashMapInsert(t, nativeMap, method, 0, methodHash);          
+          hashMapInsert(t, nativeMap, methodName(t, method), 0, byteArrayHash);
         }
       }
 
@@ -981,7 +981,7 @@ parseMethodTable(Thread* t, Stream& s, object class_, object pool)
         PROTECT(t, method);
 
         object overloaded = hashMapFind
-          (t, nativeMap, method, methodHash, methodEqual);
+          (t, nativeMap, methodName(t, method), byteArrayHash, byteArrayEqual);
 
         object jniName = makeJNIName(t, method, overloaded);
         set(t, methodCode(t, method), jniName);
@@ -1283,7 +1283,7 @@ Machine::Machine(System* system, Heap* heap, ClassFinder* classFinder):
   tenuredWeakReferences(0),
   unsafe(false)
 {
-  jni::populate(&jniEnvVTable);
+  populateJNITable(&jniEnvVTable);
 
   if (not system->success(system->make(&stateLock)) or
       not system->success(system->make(&heapLock)) or
@@ -1374,7 +1374,7 @@ Thread::Thread(Machine* m, object javaThread, Thread* parent):
     m->builtinMap = makeHashMap(this, NormalMap, 0, 0);
     m->monitorMap = makeHashMap(this, WeakMap, 0, 0);
 
-    builtin::populate(t, m->builtinMap);
+    populateBuiltinMap(t, m->builtinMap);
 
     t->javaThread = makeThread(t, 0, 0, reinterpret_cast<int64_t>(t));
   } else {
@@ -1585,6 +1585,28 @@ allocate2(Thread* t, unsigned sizeInBytes)
   }
 }
 
+object
+make(Thread* t, object class_)
+{
+  PROTECT(t, class_);
+  unsigned sizeInBytes = pad(classFixedSize(t, class_));
+  object instance = allocate(t, sizeInBytes);
+  *static_cast<object*>(instance) = class_;
+  memset(static_cast<object*>(instance) + 1, 0,
+         sizeInBytes - sizeof(object));
+
+  if (UNLIKELY(classVmFlags(t, class_) & WeakReferenceFlag)) {
+    PROTECT(t, instance);
+
+    ACQUIRE(t, t->vm->referenceLock);
+
+    jreferenceNextUnsafe(t, instance) = t->vm->weakReferences;
+    t->vm->weakReferences = instance;
+  }
+
+  return instance;
+}
+
 object
 makeByteArray(Thread* t, const char* format, ...)
 {
diff --git a/src/machine.h b/src/machine.h
index 3557243b87..7b2668d56e 100644
--- a/src/machine.h
+++ b/src/machine.h
@@ -6,8 +6,6 @@
 #include "heap.h"
 #include "class-finder.h"
 
-#define JNIEXPORT __attribute__ ((visibility("default")))
-#define JNIIMPORT __attribute__ ((visibility("hidden")))
 #define JNICALL
 
 #define PROTECT(thread, name)                                   \
@@ -22,7 +20,7 @@
 namespace vm {
 
 const bool Verbose = false;
-const bool DebugRun = false;
+const bool DebugRun = true;
 const bool DebugStack = false;
 const bool DebugMonitors = false;
 
@@ -1471,6 +1469,9 @@ makeUnsatisfiedLinkError(Thread* t, object message)
   return makeUnsatisfiedLinkError(t, message, trace, 0);
 }
 
+object
+make(Thread* t, object class_);
+
 object
 makeByteArray(Thread* t, const char* format, ...);
 
diff --git a/src/run.cpp b/src/run.cpp
index 106fcdf5f4..4409382745 100644
--- a/src/run.cpp
+++ b/src/run.cpp
@@ -76,28 +76,6 @@ popFrame(Thread* t)
   }
 }
 
-object
-make(Thread* t, object class_)
-{
-  PROTECT(t, class_);
-  unsigned sizeInBytes = pad(classFixedSize(t, class_));
-  object instance = allocate(t, sizeInBytes);
-  *static_cast<object*>(instance) = class_;
-  memset(static_cast<object*>(instance) + 1, 0,
-         sizeInBytes - sizeof(object));
-
-  if (UNLIKELY(classVmFlags(t, class_) & WeakReferenceFlag)) {
-    PROTECT(t, instance);
-
-    ACQUIRE(t, t->vm->referenceLock);
-
-    jreferenceNextUnsafe(t, instance) = t->vm->weakReferences;
-    t->vm->weakReferences = instance;
-  }
-
-  return instance;
-}
-
 inline void
 setStatic(Thread* t, object field, object value)
 {
diff --git a/test/test.sh b/test/test.sh
index 2d1a228d64..ffdeca54fb 100644
--- a/test/test.sh
+++ b/test/test.sh
@@ -36,6 +36,8 @@ for test in ${tests}; do
   fi
 done
 
+echo
+
 if [ -n "${trouble}" ]; then
-  printf "\nsee ${log} for output\n"
+  printf "see ${log} for output\n"
 fi