Jak działa Java?

Zastanawiałeś się kiedyś jak działa Java? Czym różnią się programy w niej napisane od innych aplikacji? Z czego wynikają te różnice, oraz jakie są ich pozytywne oraz negatywne strony? Jeżeli nie, to warto się nad tym zastanowić przez chwilę. W tym wpisie znajdziesz właśnie odpowiedzi na te pytania.

Zanim przejdziemy do samej Javy, omówimy najpierw jak działają programy napisane w C++ czy ObjectiveC. Otóż, kod źródłowy programu zapisany przez programistę w pliku tekstowym, tłumaczony jest przy użyciu kompilatora (proces ten nazywany jest kompilacją) na kod maszynowy.

Czym jest kod maszynowy (zwany też kodem binarnym)?

Jest to ciąg instrukcji rozumianych bezpośrednio przez procesor komputera. Efekt kompilacji zapisywany jest w pliku (w systemach Windows, są to pliki  z rozszerzeniem EXE, w przypadku Linuksa oraz OS X są to tzw. pliki binarne). W momencie uruchomienia danego programu system operacyjny wysyła zawartość takiego pliku bezpośrednio do procesora. W ten sposób kod napisany przez programistę, oraz przetworzony przez kompilator jest uruchamiany i można zobaczyć jego efekty.

Jak to działa w Java?

Programy napisane w Java nie mogą być uruchomione bezpośrednio na komputerze. Żeby je uruchomić potrzebna jest Maszyna Wirtualna Java (ang. Java Virtual Machine – JVM). To dodatkowe wymaganie niesie za sobą konkretne korzyści, ale o tym za chwilę. Skupmy się teraz ponownie na samym procesie uruchomienia aplikacji, zaczynając od jej napisania, przez kompilację aż do jej wykonania.

Po napisaniu pewnego fragmentu kodu, musisz go skompilować. W tym celu wykorzystywany jest kompilator (w przypadku Jasva jest to program o nazwie javac). Kompilator Java różni się od innych tym, że pominięty jest w nim proces optymalizacji kodu. Prawdziwa optymalizacja pod dany sprzęt odbywa się już w JVM.

Możliwe, że zastanawiasz się co to właściwie jest optymalizacja kodu. Każdy model procesora zawiera inny zestaw instrukcji. Weźmy na przykład hipotetyczny procesor P1, który posiada tylko instrukcję mnożenia. W tym przypadku jeżeli w kodzie programu użyjesz operacji potęgowania, kompilator zastąpi ją przez szereg instrukcji mnożenia.

Jeżeli za jakiś czas na rynku pojawi się procesor P2 z wbudowaną operacją mnożenia oraz potęgowania. Nasz program napisany i skompilowany wcześniej również będzie działał, ale nie będzie działał tak szybko jak to jest możliwe. W kodzie binarnym programu na stałe jest zapisane „wielokrotnie użyj operacji mnożenia”. Żeby to poprawić muszą być spełnione trzy warunki.

Po pierwsze kompilator którego używamy musi wiedzieć że ma generować kod dla procesora P2 (nie P1). Po drugie również musi wiedzieć, że procesor P2 wspiera operację potęgowania. Po trzecie, nasz program musi być jeszcze raz skompilowany.

Ponowna kompilacja pociąga za osobą jedną niedogodność, efekt wynikowy może być uruchomiony na procesorze P2, natomiast na P1 już nie zadziała. Dzięki temu zyskaliśmy pewne przyśpieszenie w programie, ale też straciliśmy możliwość uruchomienia go na procesorze P1.

W przypadku Java oraz JVM jest inaczej. W procesie kompilacji kompilator Java (javac) tłumaczy kod programu na Byte Code JVM. W czasie działania aplikacji Byte Bode jest tłumaczony, przez JVM, na kod maszynowy i wykonywany bezpośrednio na procesorze. Kod maszynowy wytworzony przez JVM nie jest nigdzie zapisywany, jest on generowany “w locie”.

Wracając do naszego przykładu z operacją potęgowania oraz procesorami P1 i P2. W przypadku Java kod aplikacji zostanie tylko raz skompilowany do pliku *.class. Żeby uruchomić naszą aplikację użytkownik musi najpierw zainstalować w swoim systemie Maszynę Wirtualną Java. Jeżeli program będzie uruchomiony na procesorze P1, operacja potęgowania zostanie wykonana jako zestaw mnożeń. W przypadku procesora P2, jeżeli użytkownik zainstalował odpowiednio nową Maszynę Wirtualną Jawy, to JVM w czasie działania programu rozpozna ona typ procesora. Wykryje możliwość zastąpienia zestawu operacji mnożenia przez pojedynczą operację potęgowania i przy następnym wykonaniu tego fragmentu Byte Code’u użyje instrukcji potęgowania.

Takie podejście sprawia, że kod aplikacji kompilujemy tylko raz, a sama optymalizacja wykonania kodu oddelegowana jest to JVM oraz zachodzi w czasie działania programu. Kiedy pojawi się nowy typ procesora program napisany w Java nie musi być kompilowany ponownie. Wystarczy, że użytkownik zainstaluje nowszą wersję JVM i wszystkie aplikacje Java będą korzystały z nowych możliwości procesora.

To podejście ma też swoje minusy. Po pierwsze użytkownik musi mieć w systemie dodatkowe oprogramowanie, żeby móc uruchomić naszą aplikację. Sama aplikacja będzie się uruchamiała odrobinę dłużej, gdyż najpierw musi zostać uruchomiona maszyna wirtualna. W czasie startu JVM wykrywa parametry systemu takie jak ilość pamięci, typ procesora itp. Na ich podstawie dokonuje pewnych wewnętrznych ustawień. Ostatnim minusem jest to, że poprawy w wydajności nie zobaczymy od razu. JVM musi się rozgrzać tj. przeanalizować dokładnie kod programu. Owa analiza odbywa się w trakcie wykonania programu, więc żeby przyśpieszyć pewien fragment kodu musi on być kilkukrotnie wykonany. Efekt rozgrzewania najlepiej widoczny jest w dużych aplikacjach serwerowych działających cały czas. W takim przypadku ma JVM spore pole do popisu.

Coraz większą popularnością cieszą się języki kompilowane do JVM Byte Code. Dla nich JVM stanowi wspólne środowisko uruchomieniowe. Możliwe że słyszałeś już o językach takich jak: Scala, Clojure, Jthon, JRuby. Łączy je właśnie możliwość uruchomienia na JVM. Posiadanie wspólnej platformy pozwala wykorzystywać istniejące już biblioteki, napisane nie tylko w tym samym języku, czy też języku Java ale również w innych językach kompilowanych do JVM Byte Code.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *