Hi all,
I wrote a relatively simple Mina app that acts as a logging "man in the
middle". It's run fine on a production server for over a year (last git
commit, 2012-12-04).
Yesterday, I put a new production server in place running Ubuntu 12.04 LTS.
pfixfilt@mail:~$ java -version
java version "1.6.0_30"
OpenJDK Runtime Environment (IcedTea6 1.13.1) (6b30-1.13.1-1ubuntu2~0.12.04.1)
OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
I am using mina-core-2.0.4, and groovy 2.0.8.
Depending on the load, the program runs out of heap on the new server, fairly
consistently. It is still running on another server that is also running
Ubuntu 12.04 LTS and the same java version. The code there has never run out
of memory, to my knowledge.
I managed to get a dump from the process the last time it ran out of memory.
My old version of jprofiler shows that most of the memory is taken up by char[]
(6,355kb), the next largest is java.lang.Class (897kb).
I'm assuming the problem is my mina code, but I can't figure out how it's
running fine on one server but not another.
My code works by extending IoHandlerAdapter. The messageRecieved method is
simple enough
@Override
void messageReceived(IoSession session, Object message) {
IoBuffer rb = (IoBuffer) message;
IoBuffer wb = IoBuffer.allocate(rb.remaining())
rb.mark()
wb.put(rb)
wb.flip()
((IoSession) session.getAttribute(OTHER_IO_SESSION)).write(wb)
rb.reset()
FileOutputStream outputStream = (FileOutputStream)
session.getAttribute(OUTPUT_STREAM)
if (outputStream != null) {
outputStream.write(wb.array())
}
}
Looking at this code, I allocate and IoBuffer that I never explicitly free, but
I don't think I can/should because of the async nature of the code.
The "inbound handler" is simply listening for connections, and when one is
received it opens an outbound connection and saves references to the "other"
connection in each session, to form a pair.
class InboundHandler extends AbstractProxyIoHandler {
private final OutboundHandler connectorHandler = new OutboundHandler()
private static final File TMP_DIR = new
File(System.getProperty('user.home') + '/tmp')
static {
if (!TMP_DIR.exists()) {
TMP_DIR.mkdirs()
}
}
NioSocketConnector connector
InboundHandler(NioSocketConnector connector) {
this.connector = connector
connector.setHandler(connectorHandler)
}
@Override
void sessionCreated(IoSession session) {
log.debug("enter sessionCreated")
InetSocketAddress remoteAddress = new
InetSocketAddress(Config.instance.data.send.host,
Config.instance.data.send.port)
connector.connect(remoteAddress).addListener(new
IoFutureListener<ConnectFuture>() {
@Override
void operationComplete(ConnectFuture future) {
try {
File outputFile = File.createTempFile("postfix-filter",
".txt", TMP_DIR)
outputFile.setReadable(true, true)
outputFile.setWritable(true, true)
log.debug("Created file: " + outputFile.absolutePath)
session.setAttribute(OUTPUT_FILE, outputFile)
session.setAttribute(OUTPUT_STREAM,new
FileOutputStream(outputFile))
future.session.setAttribute(OTHER_IO_SESSION, session)
session.setAttribute(OTHER_IO_SESSION, future.session)
IoSession session2 = future.session
session2.resumeRead()
session2.resumeWrite()
}
catch (RuntimeIoException e) {
session.close(true)
}
catch (IOException ioe) {
log.error("Unable to create tempfile: " + ioe.message, ioe)
session.close(true)
}
finally {
session.resumeRead()
session.resumeWrite()
}
}
})
}
}
When sessions are closed, I close the other end and cleanup the rest of the
resources
@Override
void sessionClosed(IoSession session) {
log.debug("Enter session closed: " + session.id)
if (session.getAttribute(OTHER_IO_SESSION) != null) {
IoSession otherSession = (IoSession)
session.getAttribute(OTHER_IO_SESSION)
log.debug("Closing session, otherSession: " + otherSession.id)
otherSession.setAttribute(OTHER_IO_SESSION, null)
session.close(false)
session.setAttribute(OTHER_IO_SESSION, null)
}
FileOutputStream outputStream = (FileOutputStream)
session.getAttribute(OUTPUT_STREAM)
if (outputStream != null) {
outputStream.close()
File file = (File) session.getAttribute(OUTPUT_FILE)
if (file != null && file.exists()) {
MessageData md = new MessageData()
file.eachLine { line -> md.processLine(line) }
}
file.delete()
}
}
Does anyone see anything obvious that I missed? I may try upgrading my mina
version to see if that helps, but unless I can find a glaring mistake I'd
rather not change the code too much.
Thank you all very much for your time, and any help you can offer.
Tony Nelson
Starpoint Solutions
Since 1982, Starpoint Solutions has been a trusted source of human capital and
solutions. We are committed to our clients, employees, environment, community
and social concerns. We foster an inclusive culture based on trust, respect,
honesty and solid performance. Learn more about Starpoint and our social
responsibility at http://www.starpoint.com/social_responsibility
This email message from Starpoint Solutions LLC is for the sole use of the
intended recipient(s) and may contain confidential and privileged information.
Any unauthorized review, use, disclosure or distribution is prohibited. If
you are not the intended recipient, please contact the sender by reply email
and destroy all copies of the original message. Opinions, conclusions and
other information in this message that do not relate to the official business
of Starpoint Solutions shall be understood as neither given nor endorsed by it.