A high-level1 VM is in many ways comparable to a scripting engine; however, with a few exceptions:
Note 1: The term high-level refers to the VM execution of bytecode which originates from the source code (the highest level), in contrast to Low-level VM Protection that uses a virtual machine to emulate a section (typically a single function) of low-level machine code.
A high-level VM is considered even more secure than a low-level one. This is because it has all the virtues of the low-level VM, but almost none of the problems. The high-level VM can execute a big program with many classes and functions as one single protected entity - not suffering from jumping in and out of protected areas. A high-level VM will also implement a much bigger and more sophisticated virtual machine.
The internal details of the execution engine (the native binary) are very general and basic with no prior knowledge nor any optimizations related to the specific bytecode being executed. This creates a big gap between, what the binary is (using statical analysis), and what it does (using memory analysis and execution interpretation).
Finally even if a cracker should identify and change the functionality of a certain part of the native binary, the consequences could be very difficult to control for the cracker, because the changed functional part could be invoked repeatedly and differently by many instructions, causing the execution to fail unintentionally and unpredictably somewhere else in the execution flow. The programmer can take advantage of this and implement a defensive programming strategy where all vital commands are used in multiple situations for multiple results, making statical cracks unsuccessful.
Using a high-level VM provides strong protection without having to hide the native instructions being executed.
Each VM can protect many classes and functions, that can't be attacked individually, requiring the cracker to invest a lot of time and effort for every application being attacked. Analysis will require complex interpretations of the memory events which makes the executed code extremely difficult to understand and virtually impossible to change.
Furthermore there is an extremely low risk of failing badly as the VM can be unique for each project, and the produced bytecode may differ for every compilation. The interpretation of the memory events may also depend on variable factors, which the programmer can control and even randomize on each execution.
Ergo: The cost for the cracker is very high.