Object pooling in a Java SE environment

With Java SE (or with another framework such as Spring) the programming models are extremely flexible. Therefore a single pooling strategy does not suit all. You should consider if there is a framework in place that could do any form of pooling, for example, Spring.

Otherwise, application logic could take this up. Ask yourself how complex is the application itself? It is best to understand the application and what it demands from the connectivity to the messaging system. Applications are often written as well within their own wrapper code around the basic JMS API.

Whilst this can be a very sensible approach, and can hide complexity, it is worth keeping in mind that it can introduce problems. For example, a generic getMessage() method, that is frequently called, should not just open and close consumers.

Points you should consider:
  • How long will the application need access to IBM® MQ? All the time, or just occasionally.
  • How often will messages be sent? The less frequently, the more a single connection to IBM MQ could be shared.
  • A connection broken exception is usually a sign of needing to re-create a pooled connection. What about:
    • Security exceptions or host not available
    • Queue full exceptions
  • If a connection broken exception occurs, what should happen to the other free connections in the pool? Should they be closed off and re-created?
  • If TLS is being used, for example, how long do you want a single connection to remain open?
  • How will a pooled connection identify itself such that a queue manager administrator can spot the connection and track it back.
You should consider all JMS objects for pooling, and pool that object whenever it is possible to do so. The objects include:
  • JMS connections
  • Session
  • Contexts
  • Producers and consumers of all different types

When using the client transport, JMS connections, sessions, and contexts, will use sockets when communicating with the IBM MQ queue manager. By pooling these objects, the savings are on the number of incoming IBM MQ connections (hConns) to the queue manager and a reduction in the number of channel instances.

Using the bindings transport to the queue manager removes the networking layer entirely. However, many applications use the client transport to provide a more highly available, and workload balanced, configuration.

JMS producers and consumers open destinations on the queue manager. If fewer numbers of queues or topics are opened, and multiple parts of the application are using these objects, pooling these can be useful.

From an IBM MQ perspective, this process saves a sequence of MQOPEN and MQCLOSE operations.

Connections, sessions, and contexts

These objects all encapsulate IBM MQ connection handles to the queue manager, and are generated from a ConnectionFactory. You can add logic to an application to constrain the number of connections, and other objects, created from a single connection factory to a specific number.

You can use a simple data structure in the application to contain the connections that are created. The application code that needs to use one of these data structures can check-out an object to use.

Take the following factors into account:
  • When should connections be removed from the pool? Generally, create an exception listener on the connection. When that listener is called to process an exception, you should re-create the connection, and any sessions created from that connection.
  • If a CCDT is in use for workload balancing, the connections could go to different queue managers. This might be applicable for the pooling requirements.

Remember, that the JMS specification states that it is a programming error for multiple threads to be accessing a session or context at the same time. The IBM MQ JMS code does attempt to be rigorous in its handling of threads. However, you should add logic to the application, to ensure that a session or context object is only used by one thread at a time.

Producers and consumers

Each producer and consumer that is created opens a destination on the queue manager. If the same destination is going to be used for a variety of tasks, it makes sense to keep the consumer or producer objects open. Only close the object when all the work is done.

Although opening and closing a destination are short operations, if they are done frequently the time taken can add up.

The scope of these objects is within the session or context they are created from, therefore, they need to be held within that scope. Generally, applications are written such that this is quite straightforward to do.

Monitoring

How will the applications monitor their object pools? The answer to this is largely determined by the complexity of the solution of pooling implemented.

If you consider a JavaEE pooling implementation, there are a large number of options, including the:
  • Current size of the pools
  • Time objects have spent in them
  • Cleaning of the pools
  • Refreshing of the connections

You should also consider how a single re-used session appears on the queue manager. There are connection factory properties to identify the application (such as appName) that could be useful.