1 - I think that this site explains the distinction pretty well: http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/architecture.html - these are basically 3 layers of abstraction built on top of each other, with JVMTI interfacing directly with the running JVM, then JDWP being used as a communication protocol, then JDI as an interface to that remote JVM. You can use a javaagent to perform bytecode instrumentation (orthogonal to the implementation of these 3 things).
2 - I think that the most performant way to do this would be to instrument all code to add a try/catch in each method to handle an exception - when the exception is caught, you handle it (however you wanted to), then re-throw it. The easiest way to do instrumentation is with the javaagent approach (then using javaassist or asm or whatever). You can instrument bytecode from JVMTI too, but it is much more cumbersome. If you only care about specific exceptions (namely, those that are explicitly thrown, and not those that are thrown internally by the interpreter, like NullPointerException, ArrayIndexOutOfBoundsException etc), then the easiest way to handle these would be to intercept ATHROW instructions (the instruction used to throw an exception). I have no specific experience, but it might be reasonable to create a JVMTI agent that is registered for the Exception event, but I am not sure of its performance (could be just as slow as your JDI approach, could be better).
3 - No: you can only instrument bytecode running in Java. If you wanted to instrument the machine code, you could try to do that with something like pin, but I think this is probably getting way out of hand for what you are looking for.
4 - Sure: what sort of static analysis are you interested in? You could certainly use something like soot, and also use JVMTI.