Jak działają kompilatory Java AOT?


18

Istnieje kilka narzędzi ( Excelsior JET itp.), Które twierdzą, że przekształcają aplikacje Java w natywne pliki wykonywalne ( *.exe). Jednak rozumiem, że te narzędzia naprawdę tworzą po prostu natywne opakowania, które wywołują / wykonują javaz powłoki lub wiersza poleceń.

Jeśli to zrozumienie jest nieprawidłowe, nie rozumiem, jak mogłoby być. Jeśli działający JVM ( javaproces) jest zasadniczo bardzo wydajnym interpretatorem, ładującym kod bajtowy z plików klas Java w locie, to nie widzę, jak aplikacja Java (zbiór plików kodu bajtowego, który służy jako dane wejściowe do JVM) naprawdę przekształcone w plik wykonywalny.

Wynika to z faktu, że proces JVM jest już rodzimym plikiem wykonywalnym, który pobiera zestawy plików kodu bajtowego jako dane wejściowe. Scalenie tych plików kodu bajtowego i procesu JVM w pojedynczy, zunifikowany natywny plik wykonywalny nie wydaje się możliwe bez całkowitego przepisania JVM i wycofania się ze specyfikacji JVM.

Pytam więc: w jaki sposób te narzędzia faktycznie „przekształcają” pliki klasy Java w natywny plik wykonywalny, czy też?

Odpowiedzi:


26

Wszystkie programy mają środowisko wykonawcze. Mamy tendencję do zapominania o tym, ale tam jest. Standardowa biblioteka lib dla C, która otacza wywołania systemowe do systemu operacyjnego. Objective-C ma środowisko wykonawcze, które otacza wszystkie przekazywane wiadomości.

W Javie środowiskiem wykonawczym jest JVM. Większość implementacji Java, które znają ludzie, jest podobna do HotSpot JVM, która jest interpretatorem kodu bajtowego i kompilatorem JIT.

To nie musi być jedyna implementacja. Nie ma absolutnie nic, co mówi, że nie można zbudować standardowego środowiska wykonawczego lib dla języka Java i skompilować kodu do natywnego kodu maszynowego i uruchomić go w środowisku wykonawczym, które obsługuje wywołania nowych obiektów w mallocach i dostęp do plików do wywołań systemowych na komputerze. I to właśnie robi kompilator Ahead Of Time (AOT zamiast JIT). Zadzwoń, że czas pracy, co będzie ... można nazwać to wdrożenie JVM (i to nie przestrzegać specyfikacji JVM) lub środowiska wykonawczego lub standardowego lib dla Javy. Jest tam i robi zasadniczo to samo.

Można to zrobić albo przez ponowną implementację, javacaby celować w maszynę natywną (tak właśnie zrobił GCJ ). Lub można to zrobić z tłumaczeniem generowanego przez bajt kodu na kod javacmaszynowy (lub bajtowy) dla innej maszyny - to właśnie robi Android. Na podstawie Wikipedii to właśnie robi Excelsior JET („Kompilator przekształca przenośny bajtowy kod Java w zoptymalizowane pliki wykonywalne dla pożądanego sprzętu i systemu operacyjnego (OS)”), i to samo dotyczy RoboVM .

Istnieją dodatkowe komplikacje związane z Javą, co oznacza, że ​​jest to bardzo trudne do wykonania jako ekskluzywne podejście. Dynamiczne ładowanie klas ( class.forName()) lub obiektów proxy wymaga dynamiki, której kompilatory AOT nie zapewniają łatwo, dlatego ich odpowiednie maszyny JVM muszą również zawierać kompilator JIT (Excelsior JET) lub interpreter (GCJ) do obsługi klas, których nie można wstępnie skompilować ojczysty.

Pamiętaj, że JVM jest specyfikacją , z wielu implementacjach . Biblioteka standardowa C jest także specyfikacją z wieloma różnymi implementacjami.

W Javie 8 wykonano sporo pracy przy kompilacji AOT. W najlepszym razie podsumowanie AOT można podsumować tylko w ramach pola tekstowego. Jednak na JVM Language Summit w 2015 r. (Sierpień 2015 r.) Odbyła się prezentacja: Java Goes AOT (wideo z YouTube). Ten film ma 40 minut i opisuje wiele głębszych aspektów technicznych i testów wydajności.


Przepraszam, ale niewiele o tym wiem, ale czy to oznacza, że ​​Java jest teraz natywna? Czy oznacza to, że istnieje nowa flaga kompilatora, która pozwala nam kompilować programy Java do kodu natywnego, jeśli chcemy, i nadal mamy opcję kompilacji do kodu bajtowego?
Pavel

@ paulpaul1076 Proponuję obejrzeć wideo, które połączyłem. Jest w tym o wiele więcej, niż mogę rozsądnie zmieścić w komentarzu.

4

gcj minimalny możliwy do uruchomienia przykład

Możesz także zaobserwować implementację typu open source, taką jak gcj(teraz przestarzała). Np. Plik Java:

public class Main {
    public static void main(String args[]) {
        System.out.println("hello world");
    }
}

Następnie skompiluj i uruchom z:

gcj -c Main.java
gcj --main=Main -o Main Main.o
./Main

Teraz możesz go zdekompilować i zobaczyć, jak to działa.

file Main.o mówi, że to plik elfa.

readelf -d Main | grep NEEDED mówi, że zależy to od bibliotek dynamicznych:

0x0000000000000001 (NEEDED)             Shared library: [libgcj.so.14]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

Więc libgcj.so musi być tam, gdzie implementowana jest funkcjonalność Java.

Następnie możesz go zdekompilować za pomocą:

objdump -Cdr Main.o

i zobacz dokładnie, jak to jest realizowane.

Wygląda bardzo podobnie do C ++, wielu zmian nazw i pośrednich wywołań funkcji polimorficznych.

Zastanawiam się, jak zaczyna się śmieciarstwo. Warto przyjrzeć się: /programming/7100776/garbage-collection-implementation-in-compiled- languages ​​i innych skompilowanych językach z GC jak Go.

Testowane na Ubuntu 14.04, GCC 4.8.4.

Spójrz również na https://en.wikipedia.org/wiki/Android_Runtime , szkielet systemu Android 5 i nowszych, który robi pełne AOT w celu optymalizacji aplikacji na Androida.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.