Szia!

Szerintem nem lepődsz meg, ha elmondom, hogy a számítógépednek korlátozott a memóriája :) Még egy merevlemez is – általában a RAM tárhelyének sokszorosa – zsúfolásig megpakolható kedvenc játékaiddal, tévéműsoraiddal, és több. Ennek elkerülése érdekében figyelnie kell a memória aktuális állapotát, és törölnie kell a szükségtelen fájlokat a számítógépről. Mi köze mindehhez a Java programozásnak? Minden! Végül is, amikor a Java gép bármilyen objektumot létrehoz, memóriát foglal le az objektum számára.

Egy igazi nagy programban objektumok tíz- és százezrei jönnek létre, és mindegyiknek külön memóriadarabja van lefoglalva. De szerinted meddig léteznek ezek az objektumok? „Élnek” a programunk teljes időtartama alatt? Természetesen nem. A Java objektumok minden előnye ellenére sem halhatatlanok :) Az objektumok saját életciklussal rendelkeznek. Ma egy kis szünetet tartunk a kódírásban, és megnézzük ezt a folyamatot :) Sőt, nagyon fontos, hogy megértse egy program működését és az erőforrások kezelését. Tehát mikor kezdődik egy tárgy élete? Mint egy ember – születésétől, azaz teremtésétől fogva.


Cat cat = new Cat(); // Here the lifecycle of our Cat object begins!

Először is, a Java virtuális gép lefoglalja a szükséges mennyiségű memóriát az objektum létrehozásához. Ezután hivatkozást hoz létre az emlékre. Esetünkben ezt a hivatkozást nevezzük cat, így nyomon tudjuk követni. Ezután az összes változóját inicializáljuk, a konstruktort meghívjuk, és — ta-da! — újonnan verett tárgyunk éli a saját életét :)

Az objektumok élettartama változó, ezért itt nem tudunk pontos számokat közölni. Mindenesetre egy ideig a programon belül él, és ellátja funkcióit. Hogy pontosak legyünk, egy objektum addig "él", amíg vannak rá hivatkozások. Amint nem marad hivatkozás, az objektum "elhal". Példa:


public class Car {
  
   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");
       lamborghini = null;

   }

}

A Lamborghini Diablo autóobjektum a metódus második sorában már nem él main(). Csak egy hivatkozás volt rá, majd ezt a hivatkozást egyenlőnek állítottuk be null. Mivel a Lamborghini Diablo-ra nem maradt fenn utalás, a lefoglalt memória „szemétté” válik. A hivatkozást nem kell nullára állítani, hogy ez megtörténjen:


public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");

       Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
       lamborghini = lamborghiniGallardo;
   }

}

Itt létrehoztunk egy második objektumot, majd ezt az új objektumot hozzárendeltük a hivatkozáshoz lamborghini. Most az Lamborghini Gallardoobjektumnak két hivatkozása van, de az Lamborghini Diabloobjektumnak nincs. Ez azt jelenti, hogy az Diabloobjektum most szemét. Ekkor lép működésbe a Java beépített, szemétgyűjtőnek (GC) nevezett mechanizmusa.

A szemétgyűjtő egy belső Java-mechanizmus, amely a memória felszabadításáért, azaz a szükségtelen objektumok eltávolításáért felelős. Jó oka van annak, hogy miért választottunk egy robotporszívót. Hiszen a szemétgyűjtő nagyjából ugyanígy működik: a háttérben "utazgat" a programodban, gyakorlatilag minden erőfeszítés nélkül összeszedi a szemetet. Feladata a programban már nem használt objektumok eltávolítása.

Ezzel memória szabadul fel a számítógépben más objektumok számára. Emlékszel, hogy a lecke elején azt mondtuk, hogy a hétköznapi életben figyelnie kell a számítógép állapotát, és törölnie kell a felesleges fájlokat? Nos, a Java objektumok esetében ezt a szemétgyűjtő végzi el helyetted. A szemétgyűjtő a program futása közben többször fut: nem kell kifejezetten meghívnia vagy parancsokat adnia, bár ez technikailag lehetséges. Később többet fogunk beszélni róla, és részletesebben elemezzük a munkáját.

Amikor a szemétgyűjtő elér egy objektumot, közvetlenül az objektum megsemmisítése előtt egy speciális metódust hív meg — finalize()— az objektumon. Ez a módszer felszabadíthatja az objektum által használt egyéb erőforrásokat. A finalize()metódus az osztály része Object. Ez azt jelenti, hogy a korábban megismert , és metódusokon kívül minden objektum rendelkezik ezzel equals()a hashCode()metódussal . toString()Abban különbözik a többi módszertől, hogy – hogy is mondjam – nagyon szeszélyes.

Különösen nem mindig hívják meg egy tárgy megsemmisítése előtt. A programozás precíz törekvés. A programozó utasítja a számítógépet, hogy csináljon valamit, és a számítógép megteszi. Gondolom, már megszoktad ezt a viselkedést, így elsőre nehéz lehet elfogadnod a következő gondolatot: "Az objektumok megsemmisítése előtt az osztály finalize()metódusa Objectmeghívásra kerül. Vagy talán nem hívják meg. Minden attól függ, a szerencséd!"

Mégis, ez igaz. A Java gép maga dönti el, hogy meghívja-e a metódust vagy sem finalize(), eseti alapon. Például próbáljuk meg kísérletként futtatni a következő kódot:


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public Cat() {
   }

   public static void main(String[] args) throws Throwable {
       for (int i = 0 ; i < 1000000; i++) {
           Cat cat = new Cat();
           cat = null; // This is when the first object becomes available to the garbage collector
       }
   }

   @Override
   protected void finalize() throws Throwable {
       System.out.println("Cat object destroyed!");
   }
}

Létrehozunk egy Catobjektumot, majd a következő kódsorban az egyetlen hivatkozását nullára állítjuk. És ezt milliószor megtesszük. Kifejezetten felülírtuk a finalize()metódust, így milliószor nyomtat egy karakterláncot a konzolra (egyszer minden alkalommal, amikor megsemmisít egy Catobjektumot). De nem! Hogy pontos legyek, csak 37 346 alkalommal futott le a számítógépemen! Vagyis 27 alkalommal csak egyszer döntött úgy a gépemre telepített Java gép, hogy meghívja a finalize()metódust.

Más esetekben enélkül történt a szemétszállítás. Próbáld meg futtatni ezt a kódot magadnak: valószínűleg más eredményt fog kapni. Amint látja, finalize()aligha nevezhető megbízható partnernek :) Szóval egy kis tanács a jövőre nézve: ne hagyatkozz a finalize()módszerre a kritikus erőforrások felszabadítására. Lehet, hogy a JVM hívja, vagy talán nem. Ki tudja?

Ha az objektum életben van, olyan erőforrásokat tartalmaz, amelyek rendkívül fontosak a teljesítmény szempontjából, például egy nyitott adatbázis-kapcsolat, akkor jobb, ha létrehoz egy speciális metódust az osztályban, hogy felszabadítsa őket, majd kifejezetten meghívja, amikor az objektum már nem szükséges. Így biztosan tudni fogja, hogy programja teljesítménye nem fog csorbulni. Kezdettől fogva azt mondtuk, hogy a memóriával való munka és a szemét eltávolítása nagyon fontos, és ez igaz is. Az erőforrások nem megfelelő kezelése és a szükségtelen objektumok tisztításának félreértése memóriaszivárgáshoz vezethet. Ez az egyik legismertebb programozási hiba.

Ha a programozók helytelenül írják be a kódjukat, előfordulhat, hogy minden alkalommal új memóriát foglalnak az újonnan létrehozott objektumok számára, míg a régi, szükségtelen objektumok nem állnak rendelkezésre a szemétgyűjtő általi eltávolításra. Mivel analógiát végeztünk egy robotporszívóval, képzeljük el, mi történne, ha a robot elindítása előtt zoknit szórunk a házban, összetörünk egy üvegvázát, és egy Lego építőkockát hagyunk a padlón. A robot természetesen megpróbálja elvégezni a dolgát, de egy ponton elakad.

Ahhoz, hogy a robotporszívó megfelelően működjön, a padlót jó állapotban kell tartani, és el kell távolítania mindent, amit a robot nem tud kezelni. Ugyanez az elv vonatkozik a Java szemétgyűjtőjére is. Ha sok olyan tárgy maradt a programban, amelyet nem lehet kitakarítani (például egy zokni vagy Lego építőelem robotporszívónkhoz), egy ponton elfogy a memória. És lehet, hogy nem csak az Ön programja fog lefagyni – a számítógépen futó összes többi program is érintett lehet. Lehet, hogy nekik sem elég a memóriájuk.

Ezt nem kell megjegyezni. Csak meg kell értened a működési elvet.