Historically there were two low level APIs within the JVM:
Both of these were deprecated in Java 5 and removed in Java 6. The replacement introduced in Java 6 was the JVM Tools Interface. This is a native C API but there is an equivalent available in two parts in pure Java. The Java Debug Interface and the java.lang.instrument package. One of the most noticeable changes made in the switch from JVMPI to JVMTI was the reliance on bytecode instrumentation for tracking method entry/exit and object allocation. The justification for this was to allow the profiling code to be optimised along with the code being profiled to minimise the overall performance impact.
In my opinion the most interesting part of all of this is that the current JDI and java.lang.instrument APIs allow for interesting debugging and profiling tools to be written in pure Java. In other words, if you are an experienced Java developer, it is relatively easy for you to write great tools that interact with the internals of the JVM and the running of Java program's.