Its just the Java VM calling into native code. Pretty sure no exceptional OS feature is needed for that beyond calling a function in a library.
assume the Android operating system has some layer built in to allow the two to communicate with each other much like the .NET framework.
Although as swiftcoder mentioned, Java's particular native interfaces are awful. I have no experience making them, but just for using them you have to forego a couple of things. For example, data has to be marshalled between what the native code sees and what you sent it from JVM run code. Things like endianess, liveness of the pointers, and so on.
Say that you pass an array (effectively, a pointer to an array) to native code. The JVM wont probably be able to guarantee that it wont move the array in a GC pass while the native code is running (remember GC runs in a different thread), that way the native code wont be able to read nor write to it so JVMs will probably just defensively copy anything that you pass to native code. AFAIK there are explicit memory pinning mechanisms exposed in CoreCLR for example, not something you get in JVMs currently though (on the other hand, HotSpot in desktop has a very, very advanced JIT compiler, which is nice).
There are ways to explicitly use a native buffer from inside Java-land, so called "direct" ByteBuffers, aka, wrapper over a pointer returned by a malloc call. They work well, they can be read efficiently by native code, no additional copying required for just passing the data, but they're a pain in the but to use from Java and you will need additional copying from the data in your Java program to the insides of the native buffer/direct ByteBuffer.
Direct ByteBuffers have also a rather convoluted mechanism for being freed that requires a phantom reference to a cleaner object, that frees the memory when it goes out of scope (or so it would like you to think) so there isn't a deterministic (nor garbage free!) way to free unused memory, which can be a big issue, since native/direct buffers have an upper limit in JVMs (meaning you can get an out of memory error just by using too much of that specific kind of memory, regardless of you having actual memory left or not).
As you can see, it gets pretty complex really fast, its simply one of the rough spots in Java and the JVM that became obvious enough when the language got popular.