I had not realized how easy it is to write a bad MQ Java program. When I worked for IBM, I had seen java applications doing 1 million MQCONNs a day – and people complained because it was slow! I accidentally managed to recreate this when looking into another problem.
I’ll show the bad programming example – which may be typical of many applications, and I’ll show what MQ requests the various JMS methods do, and how you can tell if your application is misbehaving.
My bad java program
If I had a java program which does the following
public class ColinDemo{ public static void doit(){ while ( i< 100) { colinMQ(...) } } colinMQ() { MQQueueManager queueManager = new MQQueueManager("qMgrName"); ... queueManager.close() } }
Every time colinMQ is entered it creates a new connection to MQ. When queueManager.close() is executed, there will be an MQDISC
The program as written will cause 100 MQCONNs and 100 MQDISC… whoops.
QueueManager needs to be defined as a class variable for example
public class ColinDemo{
MQQueueManager queueManager = Null;
Then colinMQ() needs a check like
if queueManager == Null then
queueManager = new MQQueueManager(“qMgrName”);
or
public class ColinDemo{ public static void doit(){ MQQueueManager queueManager; queueManager = initColinMQ(...) while ( i< 100) { colinMQ(queueManager...) } closeqm(queueManager); } MQQueueManager initColinMQ(...){ return MQQueueManager("qMgrName") } colinMQ(qm... ) { ... } closeqm(qm) { qm.close(); } }
That is all pretty basic stuff, which is obvious once you see it. The more subtle bug is when an application calls ColinDemo.doit() multiple times. On exit from ColinDemo queueManager will be closed and will cause it do do an MQDISC, the next call to ColinDemo.doit() will require another MQCONN.
You need to have ColinDemo return instance data. For example in your highest level program you say
ColinDemo cd = new ColinDemo(“qMgrName”)
then you can use cd.doit()… instead of ColinDemo.doit(), and of course you need a cd.close() to close the connection once it has been finished with.
When you are using JMS you need to have
connection = cf.createConnection();
session = connection.createSession(…);
consumer = session.createConsumer(destination);
defined as instance variables so they are set when the thread is started in the morning, and are not changed or de-allocated at the end of the day when the high level program ends.
For example in your top level JMS program
ColinDemo cd = new ColinDemo(“MYQMGR”); for (i=0 ;i <100;i++) { Message m = cd.getMessage() }
with
public class ColinDemo{ private Connection connection = null; private Session session = null; private Destination destination = null; private MessageConsumer consumer = null; private Context context = null; private JmsConnectionFactory cf = null; private String contextFactory = null; private Hashtable<String, String> environment; ColinDemo(String QMGRNAME) { // Instantiate the initial context contextFactory = "com.sun.jndi.fscontext.RefFSContextFactory"; environment = new Hashtable<String, String>(); environment.put(Context.INITIAL_CONTEXT_FACTORY,contextFactory); environment.put(Context.PROVIDER_URL, initialContextUrl); context = new InitialDirContext(environment); cf = (JmsConnectionFactory) context.lookup(connectionFactoryFromJndi); // Lookup the destination destination = (JmsDestination) context.lookup(destinationFromJndi); connection = cf.createConnection(); session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE); consumer = session.createConsumer(destination); // Start the connection connection.start(); } public Message getMessage() { Message message = consumer.receive(timeout); } }
What goes on under the covers.
A basic JMS program looks like
String contextFactory = "com.sun.jndi.fscontext.RefFSContextFactory"; Hashtable<String, String> environment = new Hashtable<String, String>(); environment.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory); environment.put(Context.PROVIDER_URL, initialContextUrl); context = new InitialDirContext(environment); cf = (JmsConnectionFactory) context.lookup(connectionFactoryFromJndi); // Lookup the destination destination = (JmsDestination) context.lookup(destinationFromJndi); connection = cf.createConnection(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); consumer = session.createConsumer(destination); // Start the connection connection.start(); // get a message Message message = consumer.receive(timeout);
Using the distributed MQ activity trace we can see the various MQ verbs which are issued.
The program started at 09:00:30 and ended at 09:00:45
connection = cf.createConnection();
has the following accounting trace records Tid Date Time Operation CompCode MQRC HObj (ObjName) 001 2019-07-26 09:00:30 MQXF_CONNX MQCC_OK 0000 - 001 2019-07-26 09:00:30 MQXF_OPEN MQCC_OK 0000 2 ( ) 001 2019-07-26 09:00:30 MQXF_INQ MQCC_OK 0000 2 ( ) 001 2019-07-26 09:00:30 MQXF_CLOSE MQCC_OK 0000 2 ( ) 001 2019-07-26 09:00:45 MQXF_DISC MQCC_OK 0000 -
We can see the transaction starting at 09:00:30 and ending at 09:00:45.
This is querying the queue manager for
MQIA_COMMAND_LEVEL (31) MQIA_PLATFORM (32) MQIA_CODED_CHAR_SET_ID (2) MQCA_Q_MGR_NAME (2015) MQCA_Q_MGR_IDENTIFIER (2032) MQIA_ADVANCED_CAPABILITY (273)
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
produces the following trace records
Tid Date Time Operation CompCode MQRC HObj (ObjName) 001 2019-07-26 09:00:30 MQXF_CONNX MQCC_OK 0000 - 001 2019-07-26 09:00:45 MQXF_DISC MQCC_OK 0000 -
consumer = session.createConsumer(destination);
adds the following to the session data.
Tid Date Time Operation CompCode MQRC HObj (ObjName) 001 2019-07-26 09:00:30 MQXF_OPEN MQCC_OK 0000 2 (Q1 ) 001 2019-07-26 09:00:45 MQXF_CLOSE MQCC_OK 0000 2 (Q1 )
connection.start();
adds nothing
Message message = consumer.receive(timeout);
adds the following tace data to the session data.
Tid Date Time Operation CompCode MQRC HObj (ObjName) 001 2019-07-26 09:00:30 MQXF_GET MQCC_OK 0000 2 (Q1 )
So by having
connection = cf.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer = session.createConsumer(destination);
Issued just once, you will save many MQCONN, MQDISC, MQOPEN, MQCLOSE and MQINQ calls.
If you see a pattern of
Tid Date Time Operation CompCode MQRC HObj (ObjName) 001 2019-07-26 09:00:30 MQXF_CONNX MQCC_OK 0000 - 001 2019-07-26 09:00:30 MQXF_OPEN MQCC_OK 0000 2 ( ) 001 2019-07-26 09:00:30 MQXF_INQ MQCC_OK 0000 2 ( ) 001 2019-07-26 09:00:30 MQXF_CLOSE MQCC_OK 0000 2 ( ) 001 2019-07-26 09:00:45 MQXF_DISC MQCC_OK 0000 -
You are doing too many connects – and need to look into it!
I am working on some other posts for making your Message Driven Bean applications running in a web server more efficient.
Thanks to Roger Lacroix for helping me get the java right. His comments reminded me of the rules of computing
- If you open it, close it
- If you allocate it, free it
- Clean up after you.
- Be a good citizen
On the web there are some good example, and some bad examples. One good example had
try{ queueManager = new MQQueueManager("qMgrName"); doWork(queueManager) ... } catch (Exception je) {... } finally{ try{ queueManager.close()} catch (Exception je){...} }
When the execution left the try{} statement, the statement in the finally was always executed, so you could be sure that the queueManager object will always closed.
Other examples did not have code to close the connection, and the connection was left open until the program terminated which could be months later! Be a good citizen, free it when you do not need it.
> When colinMQ ends, variable queueManager will eventually be garbage collected, and this will cause an MQDISC to happen.
Can you please remove that line from your blog because ‘technically’, it is not true. When you say something like that then programmers will use your idea, become lazy and not disconnect from the queue manager and bad things will happen.
The MQQueueManager class does NOT have a finalize() method which would ensure that IF an instance in garbage collected, close() will be called first.
Through my testing, I believe it is the JMQI that has hooks into the JVM shutdown, and it is the JMQI that is actually issuing the MQDISC and not the Garbage Collector.
Add the following to your code at the very bottom of your main() method:
System.gc(); // call the garbage collector
try
{
Thread.sleep(30000); // sleep for 30 seconds
}
catch(Exception e)
{}
Now look in your activity trace. You will see that all 100 connections were still active until the sleep completed and the JVM terminated.
Roger’s Programming 101: If your code opens it, then make sure your code closes it. And for MQ, if your code connects to it, then make sure your code disconnects from it.
Also, I’m not a big fan of global variables. Your code should be passing the MQQueueManager and MQQueue objects to the child methods (sub-routines) rather than using global variables. i.e. change your initColinMQ() method to return the MQQueueManager object.
Regards,
Roger Lacroix
Capitalware Inc.
LikeLike
Hello Colin,
> When colinMQ ends, variable queueManager will eventually be garbage collected, and this will cause an MQDISC to happen.
Can you please remove that line from your blog because ‘technically’, it is not true. When you say something like that then programmers will use your idea, become lazy and not disconnect from the queue manager and bad things will happen.
The MQQueueManager class does NOT have a finalize() method which would ensure that an instance in garbage collected, close() will be called first.
Through my testing:
– If the MQ client program connects in bindings mode then a disconnect will never be issued!!
– If the MQ client program connects in client mode then a disconnect will be issued by the MCA listener process (amqrmppa) when the JVM terminates.
Add the following to your code at the very bottom of your main() method:
System.gc(); // call the garbage collector
try
{
Thread.sleep(30000); // sleep for 30 seconds
}
catch(Exception e)
{}
Now look in your activity trace. You will see that all 100 connections were still active until the sleep completed and the JVM terminated.
Roger’s Programming 101: If your code opens it, then make sure your code closes it. And for MQ, if your code connects to it, then make sure your code disconnects from it.
Also, I’m not a big fan of global variables. Your code should be passing the MQQueueManager and MQQueue objects to the child methods (sub-routines) rather than using global variables. i.e. change your initColinMQ() method to return the MQQueueManager object.
LikeLike