Anda di halaman 1dari 6

Plug memory leaks in enterprise Java applications

The Java automatic garbage collection process typically operates as a low-priority thread that
constantly searches memory for unreachable objects, objects not referenced by any other object
reachable by a live thread. Different JVMs use different algorithms to determine how to collect garbage
most efficiently.
In the JVM, memory is allocated in two regions:
• Stack: Where local variables (declared in methods and constructors) are allocated. Local
variables are allocated when a method is invoked and de-allocated when the method is exited.
• Heap: Where all objects created with the new keyword are allocated. Since local variables are
few in number, only primitive types and references, usually the stack will not overflow, except
in cases of unusually deep or infinite recursion. The JVM throws a Java out-of-memory error if
it is not able to get more memory in the heap to allocate more Java objects. The JVM cannot
allocate more objects if the heap is full of live objects and unable to expand further.

Causes of memory leaks in Java


The four typical causes of memory leaks in a Java program are:
1. Unknown or unwanted object references: These objects are no longer needed, but the
garbage collector can not reclaim the memory because another object still refers to it.
2. Long-living (static) objects: These objects stay in the memory for the application's full
lifetime. Objects tagged to the session may also have the same lifetime as the session, which is
created per user and remains until the user logs out of the application.
3. Failure to clean up or free native system resources: Native system resources are resources
allocated by a function external to Java, typically native code written in C or C++. Java Native
Interface (JNI) APIs are used to embed native libraries/code into Java code.
4. Bugs in the JDK or third-party libraries: Bugs in various versions of the JDK or in the
Abstract Window Toolkit and Swing packages can cause memory leaks.
Detection of memory leaks
Some approaches to detecting memory leaks follow in the list below:
1. Use the operating system process monitor, which tells how much memory a process is using.
On Microsoft Windows, the task manager can show memory usage when a Java program is
running. This mechanism gives a coarse-grained idea of a process's total memory utilization.
2. Use the totalMemory() and freeMemory() methods in the Java runtime class, which
shows how much total heap memory is being controlled by the JVM, along with how much is
not in use at a particular time. This mechanism can provide the heap's memory utilization.
However, details of the heap utilization indicating the objects in the heap are not shown.
3. Use memory-profiling tools, e.g., JProbe Memory Debugger. These tools can provide a runtime
picture of the heap and its utilization. However, they can be used only during development, not
deployment, as they slow application performance considerably.
Causes of memory leaks in enterprise Java applications
In the subsequent sections, I analyze some causes of memory leaks in enterprise Java applications using
a sample application and a memory profiling tool. I also suggest strategies for detecting and plugging
such leaks in your own projects
ResultSet and Statement Objects
The Statement and ResultSet interfaces are used with Java Database Connectivity (JDBC) APIs.
Statement/PreparedStatment objects are used for executing a SQL Statement;
ResultSet objects are used for storing SQL queries' results.
A Java Enterprise Edition (Java EE) application usually connects to the database by either making a
direct connection to the database using JDBC thin drivers provided by the database vendor or creating a
pool of database connections within the Java EE container using the JDBC drivers. If the application
directly connects to the database, then on calling the close() method on the connection object, the
database connection closes and the associated Statement and ResultSet objects close and are
garbage collected. If a connection pool is used, a request to the database is made using one of the
existing connections in the pool. In this case, on calling close() on the connection object, the
database connection returns to the pool. So merely closing the connection does not automatically close
the ResultSet and Statement objects. As a result, ResultSet and Statement will not
become eligible for garbage collection, as they continue to remain tagged with the database connection
in the connection pool.
To investigate the memory leak caused by not closing Statement and ResultSet objects while
using a connection pool, I used a sample Java EE application that queries a database table and displays
the results in a JSP (JavaServer Pages) page. It also allows you to save records to the database table.
The application is deployed in iPlanet App Server 6.0. I used JProbe to analyze the memory utilization
by the application.
The sample application uses a database table with the following structure:
ID NUMBER
NAME VARCHAR2(300)
STREET VARCHAR(500)
CITY VARCHAR(500)
STATE VARCHAR(200)
CREATEDON DATE
VERSIONNO NUMBER
DELETESTATUS NUMBER
UPDATEDBY VARCHAR(20)
UPDATEDON DATE

First, I executed the application with the Statement and ResultSet objects closed. Subsequently,
I executed the application by not closing the Statement and ResultSet objects. I did a query
operation 50 times and observed the memory usage pattern.
Scenario 1:
The database table contains 100 rows and 10 columns. ResultSet and Statement objects are
closed. The database connection is made using a connection pool. The memory usage results of this
scenario are shown in Figure 1.

Figure 1. When queried once, the heap memory usage increases by 166.308 KB. Click on
thumbnail to view full-sized image.
Figure 1 is a heap usage chart provided by JProbe. It gives a runtime summary of the heap memory in
use over time as the Java EE application runs. The green area indicates the heap usage. The vertical line
indicates a heap usage checkpoint has been set at that time. After setting the checkpoint, the query
occurs and the heap memory usage shoots up as objects are created. Once the operation completes, the
objects no longer referenced will be garbage collected by the JVM, so the memory usage decreases.
Ideally at this time, all new objects should be released and garbage collected, and the heap usage
should return to the value before the checkpoint was set. In this case, some new objects continue to
occupy memory space, reflecting an increase in heap usage by 166.308 KB.
When queried 10 times, the heap memory usage increases by 175.512 KB, as illustrated in Figure 2.

Figure 2. Ten queries. Click on thumbnail to view full-sized image.


When queried 50 times, the heap memory usage increases by 194.128 KB, as shown in Figure 3.

Figure 3. Fifty queries. Click on thumbnail to view full-sized image.


The observed increase in memory was traced to the connection objects stored in the pool for
subsequent reuse.
Scenario 2:
The database table contains 100 rows and 10 columns. ResultSet and Statement objects are not
closed. The database connection is made using a connection pool.
When queried once, the heap memory usage increases by 187.356 KB, as shown in Figure 4.

Figure 4. Results from one query. Click on thumbnail to view full-sized image.
When queried 10 times, the heap memory usage increases by 217.016 KB.

Figure 5. Ten queries. Click on thumbnail to view full-sized image.


When queried 50 times, the heap memory usage increases by 425.404 KB

Figure 6. Fifty queries. Click on thumbnail to view full-sized image.


The difference in memory usage after 50 queries with open ResultSet and Statement objects is
231.276 KB. These results show that over time, these objects will cause a huge memory leak, thereby
generating an OutOfMemoryError.
In addition to the heap usage chart, JProbe also provides a runtime view of class instances in the heap.
From the class instance summary, we can identify the objects present in the heap at any point in time.
Figure 7 shows a part of the class instance view of Scenario 2.

Figure 7. Class instance summary. Click on thumbnail to view full-sized image.


Figure 7 clearly shows that 50 objects of OracleStatement, 500 objects of DBColumn, etc., exist
in the heap and are not garbage collected. JProbe provides a reference/referrer tree for each class
instance in the table, shown in Figure 8. From this tree we can identify how each class instance was
created.
Figure 8. Referrer tree for the DBColumn object
From the referrer tree of DBColumn, we can see that it is created by the OracleStatement object.
The class oracle.jdbc.driver.OracleStatement is the implementation for the
Statement interface. So by closing the Statement object, all associated DBColumn objects will
be garbage collected.
Recommendation
When using connection pools, and when calling close() on the connection object, the connection
returns to the pool for reuse. It doesn't actually close the connection. Thus, the associated Statement
and ResultSet objects remain in the memory. Hence, JDBC Statement and ResultSet objects
must be explicitly closed in a finally block.
Collection objects
A collection is an object that organizes references to other objects. The collection itself has a long
lifetime, but the elements in the collection do not. Hence, a memory leak will result if items are not
removed from the collection when they are no longer needed.
Java provides the Collection interface and implementation classes of this interface such as
ArrayList and Vector. Using the same Java EE application tested in the previous scenario, I
added the database query results to an ArrayList. When 35,000 rows were present in the database
table, the application server threw a java.lang.OutOfMemoryError, with a default JVM heap
size of 64 MB.

Figure 9. Heap summary when JVM throws java.lang.OutOfMemoryError. Click on thumbnail


to view full-sized image.
A collection with no policy for removing data causes a memory leak, known as the Leak Collection
anti-pattern (read J2EE Design Patterns, for more information on anti-patterns).
Recommendation
When collections are used, the object references stored in the collections should be programmatically
cleaned to ensure that the collection size does not grow indefinitely. If the collection is being used to
store a large table's query results, data access should be completed in batches.
Static variables and classes
In Java, usually a class member (variable or method) is accessed in conjunction with an object of its
class. In the case of static variables and methods, it is possible to use a class member without creating
an instance of its class. A class with static members is known as a static class. In such cases, before a
class instance is created, an object of its class will also be created by the JVM. The class object is
allocated to the heap itself. The primordial class loader will load the class object. In the case of static
classes, all the static members will also be instantiated along with the class object. Once the variable is
initialized with data (typically an object), the variable remains in memory as long as the class that
defines it stays in memory. If the primordial class loader loads class instances, they will stay in
memory for the duration of the program and are not eligible for garbage collection. So static classes
and associated static variables will never be garbage collected. Thus, using too many static variables
leads to memory leaks.

Anda mungkin juga menyukai