That is why when you research old papers about compilers, Assembly opcodes and Bytecode are often intermixed.
It was common for the OS, language runtime to work with bytecodes, with the execution being done at microcode level. What nowadays would be better done as FPGA.
On IBM i (aka OS/400), there is a kernel JIT and all languages target bytecode, including C. Also JVM bytecodes get translated into IBM I ones. It was implemented in PL/M, nowadays with many modules ported to C++. And if you want to generate actual machine code, you need the Metal C compilers.
There are plenty of other examples.
Like with containers, it is just recycling mainframe success stories.
So it's a complex topic.
The specs of the various devices have changed over time. Android scales better: you can give it very little RAM, or a lot, and it'll make best use of it by e.g. keeping more background apps loaded at once. iOS was at least historically much less flexible about this: boosting RAM was not worth as much as in the Android space because app devs would still target older devices for quite a while and so the additional RAM would go unused, and the OS wasn't capable of using the spare for much due to the lack of multi-tasking.
Eventually Apple implemented Android-style task switching, so I don't know if that's still true. I haven't done any mobile dev for years. But I also think at some point Apple realised nobody who buys an iPhone actually cares about whether they're getting value for money or what the specs are, so they just stopped competing on that area. I mean they have never reduced the price of the iPhone once, right? Despite the huge fall in underlying component prices over the years. They could ship a device with 128mb of RAM and if it did the same thing as the iPhone 4 people would still buy it, simply because they see themselves as iPhone users and not "smartphone users.
ObjC, as typically written (i.e. more object-oriented than C), is also pointer and allocation heavy. so that might make compaction more of a benefit for Android. I wonder what proportion of memory in a typical Android application process is on the C heap rather than the garbage-collected, compacted heap. For example, where does image data usually end up?
To throw in another complication, are there any significant problems that come with layering a garbage-collected runtime on top of a high-level framework based on C heap allocation and reference counting? That's what Xamarin, React Native, and AOT Java runtimes (e.g. Multi OS Engine) do on iOS, and what .NET (even .NET Native) does on UWP. Or how about two garbage-collected runtimes in one process, e.g. Xamarin and React Native on Android?
Windows Phone .NET runtime also uses tracing GC, although the underlying UWP APIs are COM based.
Does Android use more memory than iOS?
With Android there are hundreds or thousands of combinations of various bits of hardware and drivers, with various levels of compatibility. Device manufacturers have a terrific advantage in being able to produce hardware that is more specialised for the purpose, but there are trade-offs. Compiling all possible variations AOT away from the end device is going to be difficult.
And then you'd still have to fall back to on-device compilation if Play Store doesn't know what the device is anyway (aka, all custom ROMs), or when you take an OTA (otherwise you'd need to re-download all installed apps for the new OTA image).
Plus, the dex bytecode is considerably smaller than the AOT result, so wire transmission costs are cheaper doing on-device compilation - call it a form of compression.
I guess nothing would stop the Play Store from AOT compiling binaries just for users with fast connections though.
WP 8 and 8.1 use MDIL, Machine Dependent Intermediate Language, basically machine code with jump targets kept as symbolic links, replaced at installation time.
WP 10 uses .NET Native.
I can gladly point out the respective Microsoft documentation, BUILD and Channel 9 presentations.
The difference from Apple and Microsoft's bytecode solutions versus Google, is that the AOT compilation of bytecode takes place at the store, instead of the device.
dalvik's GC was a big problem for smooth UIs, though.
In the years since I've seen Android go from interpreter, to interpreter+JIT, to AOT, to JIT+AOT, to interpreted + JIT then AOT at night. V8 has gone from only JIT (compile on first use) to multiple JITs to now, interpreter+single JIT. MS CLR still doesn't have any interpreter and is fully AOT or JIT depending on mode. HotSpot started interpreted, then gained a parallel fast JIT, then gained a parallel optimising JIT, then went to a tiered mechanism where code can be interpreted, compiled and recompiled multiple times before the system stabilises at peak performance.
Looking back, it's apparent that Android's and indeed Java's initial design was really quite insightful. A tight bytecode interpreter isn't quite as awful as it sounds, especially given how far CPU core execution speed has raced ahead of memory and cache availability. If you can fit an interpreter almost entirely in icache, and pack a ton of logic into dense bytecode, and if you can use spare cores that would otherwise be idle (in desktop/mobile scenarios) to do profile guided optimisation, you can end up utilising machine resources more effectively than it might otherwise appear.