Skip to content

Latest commit

 

History

History
283 lines (201 loc) · 6.71 KB

File metadata and controls

283 lines (201 loc) · 6.71 KB

Illustrating the Java and .NET virtual machines

Chapter 9 discusses some widely used real-world abstract machines, and in particular the Java Virtual Machine and the .NET virtual machine. We present JVM and .NET bytecode which are the languages generated by Java and C# compilers and which are executed by these abstract machines. We also show an example of how a program is compiled from Java to Java bytecode (by the Java compiler) and further to real machine code (by the Java virtual machine).

The folder Virtual contains the files mentioned below.

A. Folder Selsort

Folder Selsort contains implementations of selection sort in Java and C#.

This will compile the C# version in Debug mode to a file bin/Debug/net10.0/Selsort.dll:

cd Selsort    
dotnet build Selsort.csproj

You can run the program with

dotnet run 

To disassemble Selsort.dll, you need the tool ilspycmd. Check whether ilspycmd is already installed:

dotnet tool list -g
Package Id      Version         Commands
----------------------------------------
ilspycmd        10.0.1.8346     ilspycmd

If not installed, use

dotnet tool install --global ilspycmd

The tool ilspycmd is put into folder ~/.dotnet/tools and the path should be included in your PATH environment variable.

You can disassemble the .NET bytecode of Selsort to get a file Selsort.il containing the disassembled bytecode in text form:

ilspycmd -il bin/Debug/net10.0/Selsort.dll > Selsort.il

Compile the Java version and then disassemble its Java Virtual Machine bytecode into text file Selsort.jvmbytecode:

javac Selsort.java
javap -verbose -c -p Selsort > Selsort.jvmbytecode

B. CircularQueue.java and CircularQueue.cs

The programs CircularQueue.java and CircularQueue.cs implement a generic class CircularQueue<T>. Compiling and then disassembling them to bytecode shows that the type parameters such as T have been erased in the Java bytecode, whereas they are preserved in the .NET bytecode, although renamed to !T or similar.

This will compile and run the C# program in folder CircularQueue:

cd CircularQueue
dotnet build CircularQueue.csproj
dotnet run

To put the program's .NET bytecode in text file CircularQueue.il:

ilspycmd -il bin/Debug/net10.0/CircularQueue.dll > CircularQueue.il

Compile the Java version and disassemble its bytecode into file CircularQueue.jvmbytecode:

javac CircularQueue.java 
javap -verbose -c -p CircularQueue > CircularQueue.jvmbytecode

C. StringConcatSpeed.java and StringConcatSpeed.cs

The programs StringConcatSpeed.java and StringConcatSpeed.cs demonstrate how repeated string concatenation using the (+) operator is very slow and stresses the garbage collector in both languages.

To compile and run the Java program, with verbose garbage collection messages:

cd StringConcatSpeed
javac StringConcatSpeed.java
java -verbosegc StringConcatSpeed

To compile and run the C# program in folder StringConcatSpeed:

dotnet build -c Release StringConcatSpeed.csproj
dotnet run

To investigate dotnet runtime heap allocation, install and use the dotnet-counters tool:

dotnet tool install --global dotnet-counters

The use two terminal windows. In terminal window A, run StringConcatSpeed:

dotnet run

which quickly stops with the "Press return to continue..." prompt.

In another terminal window B, run dotnet-counters to collect standard System.Runtime counters for the StringConcatSpeed process (name gets truncated to StringConcatSpe):

dotnet-counters collect -n StringConcatSpe

Back in terminal window A, press return so that StringConcatSpeed uses String operator "+" repeatedly to create a growing string.

Over in terminal window B, dotnet-counters has created a file counter.csv in comma-separated format.

Use Excel or another spreadsheet program to open that file. Add column filters with Home > Sort & Filter > Filter. In the Counter Name column, select the counter "dotnet.gc.heap.total_allocated (By / 1 sec)". This will typically show in column Mean/Increment that the .NET runtime allocates 12-13 GB per second. In total 240 GB data was been allocated, which is roughly 100,000 times more than the size of the final 2.6 MB string. Repeated concatenation with "+" is clearly a wasteful way to build strings.

D. The C# program Square.cs is in folder Square

Compile in Debug mode, run, and put the bytecode code in Square-debug.il:

cd Square
dotnet build Square.csproj
dotnet run
ilspycmd -il bin/Debug/net10.0/Square.dll > Square-debug.il

Then compile with option -c Release instead and investigate the bytecode in Square-release.il:

dotnet build -c Release Square.csproj
ilspycmd -il bin/Release/net10.0/Square.dll > Square-release.il

E. Ex26.java and Ex26.cs

Folder Ex26 contains programs Ex26.java and Ex26.cs with method isPrime:

cd Ex26
dotnet build -c Release Ex26.csproj
dotnet run 1000000
ilspycmd -il bin/Release/net10.0/Ex26.dll > Ex26.il

To investigate the machine code generated by the .NET runtime JIT compiler, set the environment variable DOTNET_JitDisasm as follows:

  • Linux and MacOS in bash: export DOTNET_JitDisasm="isPrime"

  • Windows Command Prompt: set DOTNET_JitDisasm="isPrime"

  • Windows Powershell: $env:DOTNET_JitDisasm="isPrime"

Then run the compiled Ex26.cs program:

dotnet run -c Release 10000000

To compile Ex26.java and disassemble it to bytecode:

javac Ex26.java
java Ex26 1000000
javap -c -p Ex26 > Ex26.jvmbytecode

To investigate the machine code generated by the JVM JIT compiler. To get readable symbolic machine code you need the Hotspot disassembler dynamic library, typically called hsdis-aarch64.dylib or the like. Then run:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly Ex26 10000000

This will produce a large amount of text output. You can look for the machine code corresponding for method isPrime by searching for Ex26::isPrime

F. Compiling and running ex6java in Folder Ex6

cd Ex6
javac ex6java.java
java ex6java
javap -c -p LinkedList

G. Ex13.java and Ex13.cs

Similar to Ex26.java above.