LibGDX Mälu Haldamine

1. Sissejuhatus

Mälu haldamine on oluline aspekt suurte mängude arendamisel, kuna see mõjutab nii mängu jõudlust kui ka stabiilsust. Graafika, heliefektid ja muud ressursid võivad võtta palju mälu ning enamik neist ei ole hallatud Java prügikoguja (Garbage Collector) poolt. Selle asemel hallatakse neid otse süsteemitasandil, näiteks GPU või helidraiverite poolt. Seetõttu on oluline ressursside eluiga teadlikult hallata ja need õigel ajal vabastada.

1. "Disposable" Liides

LibGDX-is on paljud klassid märgistatud liidesega Disposable, mis tähendab, et neid tuleb manuaalselt vabastada, kui need ei ole enam kasutusel.

Näited klassidest, mis kasutavad "Disposable" liidest:

Graafilised ressursid:

  • Texture

  • TextureAtlas

  • Pixmap

  • BitmapFont

  • Shader

  • Skin

Graafika ja joonistamine:

  • TileMapRenderer

  • Stage

  • Shape.

  • SpriteBatch

  • ShapeRenderer

  • ModelBatch

Heli ja füüsika:

  • Sound

  • Music

  • com.badlogic.gdx.physics.box2d.World

Mängu ressursside haldus:

  • AssetManager

  • FrameBuffer

  • Mesh

Kuidas "Disposable" objekte õigesti vabastada

Kui loote Disposable objekti, tuleb see pärast kasutamist vabastada meetodiga .dispose().

Näide:

Texture texture = new Texture("menu_background.png");
...
texture.dispose(); // Tekstuuri vabastamine, kui see pole enam vajalik

Kui mängul on mitu ekraani (näiteks menüü ja mänguekraan), tuleb veenduda, et ühele ekraanile loodud ressursid vabastatakse, kui mängija liigub teisele ekraanile.

@Override
public void hide() {
    texture.dispose(); // Vabastab tekstuuri, kui ekraan pole nähtav
}

1. Objektide Taaskasutamine (Object Pooling)

Mälu optimeerimiseks võib kasutada "Object Pooling" tehnikat. See on kasulik eriti siis, kui samatüübilisi objekte tuleb luua ja hävitada korduvalt (nt. kuuliobjektid "shooter" mängus). Selle asemel, et iga kord uus objekt luua, hoitakse neid mälus ja taaskasutatakse, kui objekt on vaba. Kui kõik objektid on juba kasutuses siis luuakse alles uus.

Kuulide korduvkasutamine Object Pooling tehnikaga

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Pool;

public class Bullet implements Pool.Poolable {

    public Vector2 position;
    public boolean alive;

    /**
     * Bullet constructor. Just initialize variables.
     */
    public Bullet() {
        this.position = new Vector2();
        this.alive = false;
    }

    /**
     * Initialize the bullet. Call this method after getting a bullet from the pool.
     */
    public void init(float posX, float posY) {
        position.set(posX,  posY);
        alive = true;
    }

    /**
     * Callback method when the object is freed. It is automatically called by Pool.free()
     * Must reset every meaningful field of this bullet.
     */
    @Override
    public void reset() {
        position.set(0,0);
        alive = false;
    }

    /**
     * Method called each frame, which updates the bullet.
     */
    public void update (float delta) {

        // update bullet position
        position.add(1*delta*60, 1*delta*60);

        // if bullet is out of screen, set it to dead
        if (isOutOfScreen()) alive = false;
    }
}
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pool;

public class World {

    // array containing the active bullets.
    private final Array<Bullet> activeBullets = new Array<Bullet>();

    // bullet pool.
    private final Pool<Bullet> bulletPool = new Pool<Bullet>() {
        @Override
        protected Bullet newObject() {
            return new Bullet();
        }
    };

    public void update(float delta) {

        // if you want to spawn a new bullet:
        Bullet item = bulletPool.obtain();
        item.init(2, 2);
        activeBullets.add(item);

        // if you want to free dead bullets, returning them to the pool:
        Bullet item;
        int len = activeBullets.size;
        for (int i = len; --i >= 0;) {
            item = activeBullets.get(i);
            if (item.alive == false) {
                activeBullets.removeIndex(i);
                bulletPool.free(item);
            }
        }
    }
}

See lähenemine vähendab prügikogumise koormust ja parandab jõudlust, eriti kui objekte luuakse tihti. obtain() hangib objekti, free() vabastab objekti tagasi taaskasutamiseks, et vältida pidevat uute objektide loomist ja mälu kulutamist.

1. AssetManager – Ressursside Keskne Haldamine

Kui mäng kasutab palju ressursse (tekstuure, fonte, helisid), võib olla kasulik kasutada AssetManager klassi, mis aitab neid efektiivselt hallata.

AssetManager kasutamine

AssetManager assetManager = new AssetManager();

// Laeme ressursid
assetManager.load("player.png", Texture.class);
assetManager.load("background_music.mp3", Music.class);
assetManager.finishLoading();

// Kasutame ressursse
Texture playerTexture = assetManager.get("player.png", Texture.class);
Music backgroundMusic = assetManager.get("background_music.mp3", Music.class);

// Vabastame kõik ressursid mängu lõpetamisel
assetManager.dispose();

AssetManager on kasulik, kuna see:

  • Laeb ressursse taustal, vältides mängu külmumist.

  • Vältib sama ressursi korduvat laadimist.

  • Haldab automaatselt Disposable objekte, vabastades need dispose() kutsumisel.

Siit lingilt saab lehele, kus seletatakse AssetManagerist pikemalt. https://gamedevdoc.pages.taltech.ee/assetManagement/assetManager.html

1. VisualVM – Mälulekete ja Jõudluse Diagnostika

VisualVM on Java rakendus, mis aitab tuvastada mälu lekkeid ja jälgida mängu jõudlust reaalajas.

VisualVM-i kasutamine:

1. Käivita oma mäng ja ava VisualVM. Link allalaadimiseks - https://visualvm.github.io/

  1. Leia protsess, mis vastab sinu mängule.

1

Vasakul on näha lokaalselt töötavad programmid. Antud juhul näha, et mänguserver töötab lokaalselt.

  1. Kasuta Monitor vaadet, et jälgida:

    • CPU kasutust

    • Mälu kasutust

    • Objektide arvu

  2. Kui mälu kasutus kasvab pidevalt ja ei vähene, võib see viidata mälulekkele.

  3. Kasuta Perform GC nuppu, et sundida prügi kogumist ja vaadata, kas mälu vabaneb korralikult.

    2

VisualVM on hea tööriist, et tuvastada mälu lekkeid, ehk eelnevalt mainitud olukorda, kus programm ei vabasta mälu. Selline olukord võib tekkida ka objektidega, mida haldab Garbage Collector automaatselt.

Näide, mida Garbage Collector automaatselt haldab:

Java Garbage Collector vastutab objekti mälu vabastamise eest, kui seda enam ei kasutata. See hõlmab selliseid objekte nagu ArrayList, HashMap ja String, mis kõik on heap mälu sees ning neid haldab GC.

  • ArrayList: Kui loome ArrayList objekti ja lisame sinna andmeid, siis GC jälgib, et see mälu vabaneks, kui listi objekti enam ei kasutata. Näiteks:

    ArrayList<String> list = new ArrayList<>();
    list.add("Test");
    
    // Kui 'list' enam ei ole kasutuses, siis GC võib selle mälu vabastada.
    list = null;  // See seab viite nulliks, tähistades, et enam ei kasutata
    
  • HashMap: Kui loome HashMap objekti, mis sisaldab võtmeid ja väärtusi, siis pärast nende eemaldamist või kui kaardiga pole enam viiteid, saab GC nende mälumahtude vabastada:

    HashMap<String, Integer> map = new HashMap<>();
    map.put("One", 1);
    map.put("Two", 2);
    
    // Kui 'map' enam ei ole kasutuses, saab GC selle vabastada
    map = null;  // Tagasi viidete puudumine tähendab, et objekt saab vabastatud
    
  • String: String objektid on samuti heap mälu objektid, mis GC haldab. Kui String ei ole enam kasutuses ja sellele ei viita ükski muu muutujat, siis GC saab selle mälumahtude vabastada:

    String text = "Hello, World!";
    
    // Kui 'text' enam ei ole kasutuses, saab GC selle vabastada
    text = null;
    

Miks see oluline on?

Näiteks koodis kus on mõni katkine lõpmatu loop, mis pidevalt teeb ja saadab packeteid, võib tekkida olukord, et packeteid tehakse kiiremini kui “garbage collector” suudab vanade packetite mälu vabastada, ning mingi hetk jookseb mäng kokku näiliselt ilma ühegi veateateta.

Korduvad VisualVM-i probleemid ja lahendused: Kõige tavalisem probleem on see, et VisualVM vajab JDK asukoha parameetrit käivitamiseks. Selle lahendamiseks on vaja minna terminalis VisualVM kausta (nt `.visualvm.exe`) ja anda järgmised parameetrid:

--jdkhome "C:\path\to\your\jdk" --userdir "C:\path\to\user\directory"

VisualVM on vahend töötavate Java rakenduste kohta info saamiseks. See on kasulik vahend, mille abil saab jälgida rakenduse jõudlust ja tuvastada erinevaid probleeme, sealhulgas mälu lekked.