11.08.2026
Karolis Tarleckas

Atminties nutekėjimai ir automatinis atminties valymas Progress OpenEdge ABL sistemose

Ilgai veikiančiose verslo aplikacijose atmintis retai kada „susitvarko savaime“ – ir Progress OpenEdge ABL nėra išimtis. Nors ši kalba turi savo įmontuotą automatini atminties valymo įrankį (angl. garbage collector), jo galimybės yra ribotos, palyginti su kitomis šiuolaikinėmis kalbomis, todėl dalis atminties valdymo vis tiek tenka kūrėjui.

Šiame straipsnyje apžvelgsime, kaip veikia OpenEdge automatinis atminties valymas, kur atmintis gali nutekėti nepastebimai, ir kaip identifikuoti bei ištaisyti šias problemas anksčiau, nei jos paveikia produkcinės sistemos stabilumą.

Automatinio atminties valymo įrankio ribotumai Progress OpenEdge sistemoje

Nors Progress OpenEdge ABL turi savo atminties valymo įrankį, jo funkcionalumas yra ribotas, palyginti su kitais tokiais įrankiais. Pagrindinis jo tikslas – patikrinti, ar klasės instancijos yra kur nors referencuojamos, ir atitinkamai imtis veiksmų. Tai reiškia, kad automatinis atminties valymo įrankis tvarko tik klasės pagrindu sukurtus objektus, kildinamus iš Progress.Lang.Object klasės. Kiti duomenų tipai, tokie kaip temp-tables, datasets ar buffers, automatiškai netvarkomi – juos reikia rankiniu būdu ištrinti arba ištuštinti, kitaip jie liks atmintyje net ir nebūdami niekur naudojami.

Atminties valymo įrankis tvarko:

  • Vartotojo apibrėžtas objektų instancijas,
  • Sistemos generuojamus klaidų (error) objektus,
  • .NET objektus.

Pastaba: handle pagrindu sukurti objektai automatiškai netvarkomi atminties valymo įrankio, o jų valdymas paliekamas kūrėjo nuožiūrai.

Kaip veikia automatinis atminties valymas?

OOABL objektas sukuriamas, ir jo nuorodų skaičius nurodomas kaip vienetas. Kiekviena papildoma nuoroda į tą pačią objekto instanciją padidina skaitiklį. Kai nuorodos į objektą atsisakoma, skaičius atitinkamai sumažėja.

Jei bet kuriuo metu nuorodų skaičius nukrenta iki nulio, objektas pridedamas į sunaikinimo (angl. destroy) sąrašą, ir atminties valymo įrankis jį pašalins kito GC ciklo metu.

GC ciklas gali būti paleistas:

  • Neveiklos (angl. Idle) metu – kai ABL runtime nevykdo veiksmų ir laukia kito įvykio ar užklausos
  • Prieš perjungiant į kitą sesijos kontekstą
  • Transakcijos bloko pabaigoje, jei nuoroda buvo UNDO-scoped

GC ciklo metu vykdomi ir naikinamų instancijų destruktoriai.

Kodo pavyzdys:

DEFINE VARIABLE o AS Progress.Lang.Object NO-UNDO. 
DEFINE VARIABLE o2 AS Progress.Lang.Object NO-UNDO. 

o = NEW Progress.Lang.Object(). // Create an object, ref count = 1 
o2 = o. // Add another reference, ref count = 2 
o2 = ?. // Drop one reference, ref count = 1 
o = ?. // Drop last reference, ref count = 0 → object added to GC destroy list 

/* At this point, GC will eventually destroy the object and call its destructor */ 

Kada GC gali vėluoti arba iš viso neįvykti

Tam tikrais atvejais automatinis atminties valymas gali būti atidėtas arba niekada neįvykti:

  • Transakcijos bloke, jei nuoroda yra UNDO-scoped. Šiuo atveju GC lauks, kol transakcija pasibaigs, ir tik tada paleis atminties valymą.  

Kodo pavyzdys: Undo_scoped_variable

DEFINE VARIABLE o AS Progress.Lang.Object. // UNDO-scoped by default 

DO TRANSACTION: 
    o = NEW Progress.Lang.Object(). // Create object, reference count = 1 
    o = ?. // Drop reference inside transaction, ref count = 0 but GC will wait for the transaction to end. 
END. 
/* Transaction ends → GC runs, object destroyed */ 
  • Jei atsiranda ciklinė nuoroda (angl. circular reference) – tokiu atveju nuorodų skaičius niekada nepasieks nulio, ir gali atsirasti atminties nutekėjimas.  

Kodo pavyzdys: Circular_reference

/* Create a class which holds an instance of itself */ 
CLASS Node: 
    DEFINE VARIABLE next AS Node. 
END CLASS. 

/* Define two instances of that class */ 
DEFINE VARIABLE a AS Node NO-UNDO. 
DEFINE VARIABLE b AS Node NO-UNDO. 

/* Create two objects referencing each other */ 
a = NEW Node(). 
b = NEW Node(). 

a:next = b. 
b:next = a. 

/* Drop external references */ 
a = ?. 
b = ?.  

/* Circular references never let refence count drop to zero → GC never deletes them -> Memory leak occurs */ 
  • Jei sukuriama Singleton instancija. Kadangi instancija nurodo pati save, nuorodų skaičius niekada nepasiekia nulio, ir GC tos instancijos niekada neištrina.

Kodo pavyzdys: Singleton

/* Create a class that references itself */ 
CLASS Singleton: 
    DEFINE VARIABLE me AS Singleton. 

    CONSTRUCTOR PUBLIC Singleton(): 
        me = THIS-OBJECT. 
    END. 
END CLASS. 

/* Declare an variable of this classes type */ 
DEFINE VARIABLE s AS Singleton NO-UNDO. 

/* Create an instance of this class  */ 
s = NEW Singleton(). 
/* External reference dropped, but self-reference never lets reference count drop to zero */ 
s = ?. 

/* GC never deletes this object because it references itself */ 

-nogc parametras

-nogc startuojantis parametras visiškai išjungia ABL automatinį atminties valymą – tai reiškia, kad objektai niekada automatiškai nesunaikinami, net jei jų nuorodų skaičius nukrenta iki nulio. Nors tai gali padėti derinimo (angl. debugging) ar našumo testavimo metu (nes išvengiama netikėtų destruktorių iškvietimų ir GC pauzių). Šis parametras turi ir reikšmingą riziką: atminties naudojimas nuolat augs, jei nenaudojami objektai kaupsis nepaleisti, o tai gali sukelti atminties nutekėjimus ir laipsnišką aplikacijos stabilumo prastėjimą.

Kaip pridėti -nogc parametrą vykdomai programai per PDSOE:

  1. Dešiniuoju pelės klavišu spustelėkite projektą, kuriam norite pridėti šį parametrą → pasirinkite „Run As" → spauskite „Run Configurations..."

2. Kairėje pusėje matysite visų projekto failų sąrašą – pasirinkite konkretų failą → eikite į skirtuką „Startup" → lauke „Additional startup parameters" įrašykite -nogc parametrą → spauskite „Apply"

Pastaba: -nogc parametrą galite taikyti tik konkretiems failams, o ne visam projektui.

Kas yra atminties nutekėjimas?

Atminties nutekėjimas (angl. memory leak) – tai situacija, kai objektui skirta atmintis niekada neatlaisvinama, net jei programai jos daugiau nebereikia. Kai įvyksta atminties nutekėjimas, nenaudojami objektai lieka atmintyje ir be reikalo eikvoja resursus. Laikui bėgant tai gali priversti aplikaciją naudoti vis daugiau atminties, dėl ko sulėtėja veikimas, padidėja apkrova automatiniam atminties valymo įrankiui, o sunkiausiais atvejais aplikacija gali strigti arba baigtis „out-of-memory" klaidomis. ABL kalboje atminties nutekėjimai dažniausiai kyla dėl ciklinių referencijų, save referencuojančių singleton'ų arba ilgai gyvuojančių nuorodų, kurios niekada neišvalomos – Progress'o žinių bazė šiuos atvejus aprašo išsamiau.

Dažniausios atminties nutekėjimo priežastys

Persistent procedūros

PERSISTENT raktažodis RUN sakinyje nurodo AVM paleisti procedūrą ir palikti ją atmintyje tol, kol ją ištrinsite arba kol pasibaigs jūsų sesija. Dėl šios priežasties, jei procedūra netinkamai valdoma ir netrinama, ji liks atmintyje ir laikui bėgant gali sukelti atminties nutekėjimą. Įsitikinkite, kad kiekviena iškviesta procedūra yra ištrinama, kad atmintis būtų atlaisvinta, nebent ji reikalinga per visą aplikacijos veikimo ciklą. Daugiau informacijos rasite Progress dokumentacijoje apie persistent procedūros vykdymą.

Kodo pavyzdys: Persistent_procedure_

/* Define the handle and set the persistent procedure */ 
DEFINE VARIABLE hOrderProc AS HANDLE NO-UNDO. 

RUN orderHandler.p PERSISTENT SET hOrderProc. 

/* After use, if deletion is not called it will reside in memory until the end of the session */ 
IF VALID-HANDLE(hOrderProc) THEN 
    DELETE PROCEDURE hOrderProc. 

Temp-tables

Jei temp-tables neišvalomos ar netrinamos, jos ir toliau naudoja atmintį visą sesijos gyvavimo laiką. Ilgai veikiančiuose procesuose tai lemia nuolatinį atminties augimą, sumažėjusį našumą, o galiausiai gali sukelti nestabilumą ar strigimus.

Kodo pavyzdys: Temp-Table.p

/* Define Temp-Table */ 
DEFINE TEMP-TABLE ttCustomer NO-UNDO 
    FIELD CustNum AS INTEGER 
    FIELD Name    AS CHARACTER. 

/* If the procedure is called multiple times, the size grows and can cause performance and memory issues */ 
PROCEDURE getCustomers:   

    EMPTY ttCustomer /* It is important to empty the temp-table before rewriting or after use */ 

    FOR EACH Customer: 
        CREATE ttCustomer. 
        ASSIGN 
            ttCustomer.CustNum = Customer.CustNum 
            ttCustomer.Name    = Customer.Name. 
    END. 
END. 

Statiškai sukurtos temp-tables gali būti perduodamos tarp AppServer'ių. Jei temp-table perduodama per INPUT-OUTPUT, INPUT ar HANDLE, papildomų veiksmų nereikia – pakanka ją ištuštinti, kai sukaupti duomenys nebereikalingi, nes jos gyvavimo ciklą valdo pati sesija. Jei sukuriama dinaminis temp-table, jos gyvavimo ciklą turi valdyti pats kūrėjas, o perduodant ją į AppServer, objektą reikia rankiniu būdu ištrinti finally bloke toje aplikacijoje, kurioje jis buvo sukurtas – nepriklausomai nuo to, ar jis perduotas kaip referencija, ar kaip kopija.

Kodo pavyzdys: Dynamic_Temp-Tables.p

DEFINE VARIABLE hTT AS HANDLE NO-UNDO. 

DO ON ERROR UNDO, THROW: 

    /* Create dynamic temp-table */ 
    CREATE TEMP-TABLE hTT. 
    hTT:ADD-FIELDS-FROM("Customer"). 
    hTT:TEMP-TABLE-PREPARE("ttCustomer"). 

    /* Pass it to the AppServer by handle */ 
    RUN getCustomers ON SERVER INPUT TABLE-HANDLE hTT. 

    /* Use the data locally */ 
END. 

FINALLY: 
    /* Correct cleanup of the dynamic temp-table */ 
    IF VALID-HANDLE(hTT) THEN 
        DELETE OBJECT hTT. 

Dinaminiai objektai

Dinaminiai objektai (angl. dynamic objects) – tai objektai, kurie sukuriami vykdymo (runtime), o ne kompiliavimo metu. Tai apima:

  • Persistent procedūras (RUN ... PERSISTENT), kaip minėta anksčiau
  • Dinamines užklausas ir buferius (CREATE QUERY, CREATE BUFFER)
  • Dinamines temp-tables ir datasets (CREATE TEMP-TABLE, CREATE DATASET)
  • GUI valdiklius (CREATE FRAME, CREATE WINDOW)
  • COM arba .NET objektus (CREATE "Excel.application")

Šie objektai užima vietą krūvoje (angl. heap) ir automatiškai vykdymo metu netvarkomi, jei jie nėra tinkamai atlaisvinami. Daugiau informacijos rasite Progress dokumentacijoje apie dinaminių objektų trynimą.

Kodo pavyzdys: Dynamic_Object.p

/* Define a dynamic handles */ 
DEFINE VARIABLE hTT AS HANDLE NO-UNDO. 
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO. 
DEFINE VARIABLE hFrame AS HANDLE NO-UNDO. 

/* Create a dynamic temp-table */ 
CREATE TEMP-TABLE hTT. 
hTT:ADD-FIELD("CustNum", "INTEGER"). 
hTT:ADD-FIELD("Name", "CHARACTER"). 

hTT:CREATE-BUFFER-HANDLE("CustomerBuffer"). 

/* Create a dynamic query on the temp-table */ 
CREATE QUERY hQuery. 
hQuery:SET-BUFFERS(hTT:BUFFER-HANDLE("CustomerBuffer")). 
hQuery:QUERY-PREPARE("FOR EACH Customer"). 
hQuery:QUERY-OPEN(). 

/* Create a GUI frame to display data */ 
CREATE FRAME hFrame 
    TITLE "Customer Info" 
    WITH FRAME fCustomer. 

/* ...populate GUI with query data... */ 

/* ---------------- Cleanup ---------------- */ 
/* If not deleted dynamic objects are lost and will reside in memory, this can cause memory leaks */ 
IF hQuery:IS-OPEN THEN 
    hQuery:QUERY-CLOSE(). 
IF VALID-HANDLE(hQuery) THEN 
    DELETE OBJECT hQuery. 

IF VALID-HANDLE(hFrame) THEN 
    DELETE WIDGET hFrame. 

IF VALID-HANDLE(hTT) THEN 
    DELETE OBJECT hTT. 

Be jau minėtų, atminties nutekėjimus gali sukelti ir šie dinaminiai objektai:

  • MEMPTR – dinaminis atminties blokas; būtina SET-SIZE(<name>) = 0
  • X-DOCUMENT – dinaminis XML dokumentas; būtina DELETE-OBJECT
  • X-NODEREF – dinaminė XML mazgo referencija; jei sukurta atskirai, būtina DELETE OBJECT
  • SOCKET – tinklo ryšys; būtina CLOSE() ir DELETE OBJECT
  • SOAP-HEADER-ENTRYREF ir SOAP-HEADER – dinaminiai SOAP objektai; būtina DELETE OBJECT
  • SERVER-SOCKET ir SERVER – dinaminiai serverio objektai; būtina CLOSE() ir DELETE OBJECT
  • SAX-WRITER, SAX-READER ir SAX-ATTRIBUTES – dinaminiai XML srautinio apdorojimo objektai; būtina DELETE OBJECT
  • DATA-SOURCE – dinaminis handle duomenų bazės užklausai; būtina DELETE OBJECT
  • CLIENT-PRINCIPAL – dinaminis handle kliento saugumo objektui; būtina DELETE OBJECT
  • BROWSE – dinaminiai GUI objektai; būtina DELETE OBJECT

Kodo pavyzdys: Extra_Example.p

/* Example: X-DOCUMENT and SOCKET with proper cleanup */ 

DEFINE VARIABLE xd AS X-DOCUMENT HANDLE NO-UNDO. 
DEFINE VARIABLE s  AS SOCKET     HANDLE NO-UNDO. 

DO ON ERROR UNDO, THROW: 

    /* Create dynamic X-DOCUMENT and populate it*/ 
    CREATE X-DOCUMENT xd. 
    xd:LOAD("sample.xml"). 

    /* Create and connect SOCKET */ 
    CREATE SOCKET s. 
    s:CONNECT("localhost", 1234). 

    /* Work with the objects */ 

END. 

FINALLY: 

    /* Proper cleanup */ 
    /* X-DOCUMENT: delete object */ 
    IF VALID-HANDLE(xd) THEN 
        DELETE OBJECT xd. 

    /* SOCKET: close and delete object */ 
    IF VALID-HANDLE(s) THEN DO: 
        s:CLOSE(). 
        DELETE OBJECT s. 

END. 

Įvykių prenumerata

Prenumeruojant įvykius sukuriama nuoroda nuo leidėjo (angl. publisher) į prenumeratorių (angl. subscriber). Jei prenumerata neatšaukiama, objektas liks atmintyje net ir tada, kai jo daugiau nebereikės – tai sukelia atminties augimą, sumažėjusį našumą ar strigimus. Visada atšaukite prenumeratą prieš ištrindami objektą.

Kodo pavyzdys: Event_Subscription.p

/* Define the handle and persistent procedure */ 
DEFINE VARIABLE hPublisher AS HANDLE NO-UNDO. 
RUN dataPublisher.p PERSISTENT SET hPublisher. 

/* Subscribe to the DataChanged event */ 
SUBSCRIBE TO "DataChanged" IN hPublisher. 

/* Later, cleanup or it can cause a memory leak */ 
IF VALID-HANDLE(hPublisher) THEN DO: 
    UNSUBSCRIBE TO "DataChanged" IN hPublisher. 
    DELETE PROCEDURE hPublisher. 
END. 

Pastaba: anksčiau minėti atvejai (ciklinė nuoroda ir Singleton instancija) taip pat gali sukelti atminties nutekėjimus, kadangi atmintis niekada neatlaisvinama.

Kaip identifikuoti atminties nutekėjimus

Atminties nutekėjimus galima identifikuoti naudojant AVM log failus ir įmontuotą LeakCheck klasę.

Pirmiausia reikia įjungti dinaminių objektų fiksavimą (angl. logging):

log-manager:logfile-name = logfile.

log-manager:log-entry-types = 'DynObjects.*:5'.

Sukurkite LeakCheck klasės instanciją (reikalingas Include OpenEdge.Core.Util.LeakCheck):

checker = new LeakCheck().

Perskaitykite log failą, kad būtų nuskaityti dinaminių objektų įrašai:

checker = ParseLog(logFile).

Jei norite bendrai patikrinti, ar yra nutekėjimų, be papildomos informacijos, galite iškviesti HasLeaks() metodą:

message 'checker:HasLeaks()=' checker:HasLeaks()

skip view-as alert-box.

Jei nutekėjimų yra, GetReport() metodas grąžina JSON failą su visa informacija apie juos:

report = checker:GetReport().

report:WriteFile(session:temp-dir + 'leak_report.json', yes).

Sukurtoje ataskaitoje rodoma informacija – kiek nutekėjimų atsirado per sesiją, kuriame faile, kurioje eilutėje ir koks veiksmas juos sukėlė. Tai leidžia lengviau derinti kodą ir greičiau ištaisyti problemas.

Pastaba: nuo PDSOE 12.8.9 versijos yra prieinamas Memory Profiler įrankis, padedantis stebėti ir diagnozuoti atminties suvartojimą per pasirinktus laiko intervalus. Daugiau informacijos rasite OpenEdge Memory Profiler įžangoje.

Svarbiausios išvados: kaip išvengti atminties nutekėjimų Progress OpenEdge ABL sistemoje

Atminties valdymas Progress OpenEdge ABL aplikacijose reikalauja atidaus dėmesio rankiniam dinaminių objektų, nenutraukiamų procedūrų, įvykių prenumeravimo ir sukauptų duomenų tvarkymui, kadangi įmontuotas automatinis atminties valymas tvarko tik klasės pagrindu sukurtus objektus. Log failais pagrįstas nutekėjimų aptikimas ir profiliavimo įrankiai žymiai pagerina galimybę identifikuoti ir ištaisyti atminties nutekėjimus, užtikrinant, kad ilgai veikiančios aplikacijos išliktų stabilios ir efektyvios.

Gaukite ekspertų pagalbą savo Progress OpenEdge sistemoms

Jei jūsų organizacija palaiko ar plėtoja Progress OpenEdge sistemas ir norite užtikrinti, kad jos ilguoju laikotarpiu išliktų stabilios ir efektyvios, Baltic Amadeus Progress OpenEdge komanda gali padėti – nuo atminties problemų diagnostikos iki platesnės sistemos modernizavimo strategijos. Susisiekite su mumis ir aptarkime jūsų projektą.

Susiję straipsniai

Pasikalbėkime apie jūsų projektą

Pradedate projektą arba norite sustiprinti jau vykdomą? Susisiekite ir atsakysime jums per vieną darbo dieną.

Parašykite mums

Ačiū! Jūsų pateikimas gautas!
Oi! Pateikiant formą kažkas nutiko.