Why care about memory management?
A common type of support case deals with (perceived) memory leak in some part of an application. Generally this occurs with dynamic programming without fully understanding the key principles.
Another common type of support case deals with perceived high memory usage - whether false positive or actual issue. Actual issues here can result in system-wide resource shortages and excessive swapping on the OS level. False positives need to be identified as such early; trying to fix these when there's no issue is counter-productive.
What is a memory leak ?
A memory leak, is where allocated memory is not freed although it is never used again. Repeated memory leaks cause the memory usage of a process to grow without bound, eventually causing performance issues and/or hard crashes.
How and when do these memory leak problems happen?
- In manual memory management, this usually occurs because objects become unreachable without being freed.
- In tracing garbage collection, this happens when objects are reachable but not live.
- In reference counting, this happens when objects are referenced but not live. (Such objects may or may not be reachable.)
Memory leaks build up over time before becoming problematic at runtime, the real issues usually happen after deployment. To notice a memory leakage during development / QA stage typically requires a specific test plan.Types of memory overview:1. Static object memory:
In the context of classes and garbage collection, the term Static object memory
is used to mean memory used to store static objects. By definition these static classes are loaded once for a session, and once allocated persist for the duration of the session.2. Stack memory:
Stack allocation means run-time allocation and de-allocation of storage in last-in/first-out order. In ABL, the programmer does not get control over the stack, that is managed only by the AVM. Application code however, can trigger the stack to be pushed over the limit.
Fixed-size memory pool, provided by the Stack Size (-s)
Client Startup parameter is used:
Stack overflow errors are most common when data definitions are loaded for very large tables, when recursive procedures are involved, or when large amounts of data are being passed as parameters.
- To hold temporary copies of data on their way from the DB or to the screen
- During computation of expressions
Increase the stack size if one of the following messages appears:
SYSTEM ERROR: stkpush: stack overflow. Increase -s parameter.
3. Widget Pools:
SYSTEM ERROR: stkditem: stack overflow. Increase -s parameter.
Widget Pools are used to manage traditional dynamic objects
, they are not used for ABL Class instances
4. External Memory:
- Unnamed and named pools
- Unnamed are scoped to the procedure in which they are defined
- Named are scoped to the session
- Allocated as coded by the programmer
5. Memory Pointers:
- ActiveX, COM-HANDLEs and DLLs
MEMPTR variables in ABL are the equivalent of byte arrays for most purposes.
However, when interacting with DLLs / Shared Libraries they also are used as actual pointers to memory allocated by the external library.Types of Objects overview1. Static ABL Objects:
- A static object is statically defined in the code.
- Static objects are bound at compile time and scoped to the instantiating program. Memory is allocated when the program starts executing, and freed when the procedure.p terminates.
- As example, this code will implicitly define 3 static objects: a frame and 2 fill-ins to display the 2 fields:
FOR EACH CUSTOMER:
DISPLAY CUSTOMER.CUSTNUM CUSTOMER.NAME.
2. Dynamic Objects:
- Dynamic Objects are created and bound at run time. The compiler does not know that they exist.
- Heap allocation or dynamic allocation means run-time allocation and de-allocation of storage in arbitrary order.
- Dynamic allocation is usually for objects whose size, quantity, or lifetime, could not be determined at compile-time.
- Memory is only allocated when the CREATE.... SET or RUN...... ASYNC statement executes. This memory is not garbage collected automatically.
- To avoid problems of leakage with dynamic objects use DELETE OBJECT, or widget pools.
DEFINE VARIABLE hQry AS HANDLE NO-UNDO.
CREATE QUERY hQry.
REPEAT WHILE NOT hQry:QUERY-OFF-END:
DELETE OBJECT hQry.
3. Handles to Objects:
4. Garbage collection (GC):
- ABL Handles are a type of variable used for referencing internal object types, they are not objects themselves.
- A handle can go out of scope while the object it points to remains, or an object can be removed from memory while there are still handles referring to it.
5. ActiveX Controls:
- Garbage collection is also known as automatic memory management, which is the automatic clean up of unused objects, and freeing memory.
- Garbage collection is performed by a garbage collector which recycles memory that it can prove will never be used again.
- AVM implements a Garbage Collector for ABL Classes.
- A COM object property is a value that defines visible, functional, and other characteristics of a COM object (ActiveX Automation object or ActiveX control).
- An ActiveX control's property is classified as a design-time or run-time property depending on when you can change it.
- A design-time property can be changed using the Properties Window of the AppBuilder.
- A run-time property can be changed from the ABL at run time.
- Generally, both design-time and run-time properties can be read at run time.
- In all other respects, COM object properties are functionally analogous to widget attributes.
- A COM object method is a specialized function associated with a COM object that performs an action on the COM object or alters the behavior of the COM object. COM object methods may or may not return a value and may or may not require parameters. A return value may be a component handle to another COM object; however, many methods return other types of information or no information at all. Like widget methods, COM object methods can executed by direct invocation, as statements, rather than by invocation as part of an expression.
- In all other respects, COM object methods are functionally analogous to widget methods. The basic syntax for referencing COM object properties and methods from the ABL is similar to that of referencing widget attribute and method references.
The main differences include:
- COM object property and method references can be chained to turn component handles into a single reference. Thus, a COM-handle property value may be used to invoke a method of the referenced object, and a method return value can be used to reference a property.
- The parameters of some COM object methods might have to be supplied with more type information, depending upon the methods and how the COM objects are implemented.
- All COM objects are dynamic objects, so there is never a need to qualify a COM object reference using a static container reference (such as one might do with a static FRAME or MENU widget).
- The portion of memory a COM object uses is allocated outside the scope of the ABL. It is only accessible by COM-HANDLE variables.
- Any time a COM object reference is stored in a COM-HANDLE variable it must be release using the RELEASE OBJECT statement.
- The AppBuilder, operating in Design Mode, provides facilities for setting design-time properties for ActiveX controls + control-frame containers.
- To access an ActiveX control that has been loaded into a session at run-time, use the control-frame COM-HANDLE attribute to get a handle to the control-frame COM object. To return a handle to the control, use the design-time name of the ActiveX control as a property of the control-frame COM object.
Example: The following procedure fragment shows a control named hc_CmdButton
being loaded into a control-frame and the handle to the control (controlHdl
) is obtained using the control name (hc_CmdButton
) property. Later it releases the control and deletes the parent control-frame widget. (CFWidHdl
DEFINE VARIABLE CFWidHdl AS WIDGET-HANDLE.
DEFINE VARIABLE CFComHdl AS COM-HANDLE.
DEFINE VARIABLE controlHdl AS COM-HANDLE.
/* Create frame foo ... */
CREATE CONTROL-FRAME CFWidHdl
ASSIGN FRAME = FRAME foo:HANDLE
NAME = "ctlFrame1".
CFComHdl = CFWidHdl:COM-HANDLE.
controlHdl = CFComHdl:hc_CmdButton.
controlHdl:BgColor = RGB-VALUE(0,128,0).
/* do some more stuff ... WAIT-FOR ... */
RELEASE OBJECT controlHdl.
DELETE WIDGET CFWidHdl.
A shared library is a file that contains a collection of compiled functions (routines) that can be accessed by applications. Such a file is called a shared object or shared library (.so) on UNIX and a Dynamic Link Library (.DLL) on Windows.
An application links to these routines at run-time rather than at compile/build-time, and shares the functionality of the code with other applications that can link to them. Shared libraries promote:
- code re-use, because an application can reference third-party logic) and
- upgradeability, because any enhancement to a shared library becomes immediately available to an application, without rebuilding it.
8. SET-POINTER-VALUE Function:
The ABL enables linking to and executing shared library routines by defining an "EXTERNAL" procedure, using much the same syntax as a standard "INTERNAL" procedure:
PROCEDURE GetWindowRect EXTERNAL "USER32.DLL":
DEFINE INPUT PARAMETER hWnd AS LONG.
DEFINE OUTPUT PARAMETER lpRect AS MEMPTR.
7. Persistent Option:
The ABL supports the PERSISTENT option on the entry point definition. It is no longer necessary to call LoadLibrary
to ensure that a DLL remains in memory until the process is exiting, or FreeLibrary
is explicitly called.
- The ABL supports a SET-POINTER-VALUE function that allows setting the value of a MEMPTR variable.
- SET-POINTER-VALUE is used to map a MEMPTR to memory allocated by a Windows Dynamic Link Library (DLLs) or UNIX shared library routine called from the ABL.
Example: The following code example calls a DLL routine that returns a pointer to a structure, extracts an address at byte 5 of the structure, uses SET-POINTER-VALUE to assign the address to a Progress MEMPTR, and displays the character string at the address.
DEFINE VARIABLE person_struct AS MEMPTR. /*pointer to structure */
DEFINE VARIABLE name AS MEMPTR. /* pointer to name */
SET-SIZE(person_struct) = 8.
PROCEDURE person_info EXTERNAL "person.dll" PERSISTENT:
DEFINE OUTPUT PARAMETER person_struct AS MEMPTR.
RUN person_info(OUTPUT person_struct).
SET-POINTER-VALUE(name) = GET-LONG(person_struct,5).
DISPLAY GET-STRING(name,1) FORMAT "x(50)".