When using JAXB and Jersey make sure you configure the –Xms property to tune your JVM initial memory allocation. If you forget to do this you’ll likely end up with very poor performance caused by garbage collector thrashing.
Prior to discovering this problem I was a bit naïve about garbage collection (actually I must admit that I still am). I knew that big high performance production servers benefited from lots of memory configuration but I thought that smaller test and development environments would survive just fine mostly on JVM defaults. For these smaller environments I would just set –Xmx. My naïve assumption was that memory would start small and then grow as needed to the maximum configured.
What I failed to realize is that there are many different partitions of the memory space and that some of these areas are allocated at JVM startup and never grow. In other words, their size is only affected by the initial memory allocation and not the maximum memory allocation. It turns out that certain aspects of Jersey and JAXB can be very sensitive to these initial allocations. This means that if you use Jersey and JAXB you should configure a larger initial memory size even in simple test and development environments and certainly in larger production environments.
The problem is related to short lived temporary objects. It turns out that it doesn’t take much to encounter the problem. It doesn’t take a massive multithreaded program with lots of allocated objects. I created a single threaded one page program that exhibits the problem. If you run this program under a 32-bit JVM then you will experience the symptom first hand. Try running the program with different initial memory allocations and see what you get.
I first noticed the problem when working with Jersey to do high-performance multipart content transfer. There are a couple different modes of HTTP content transfer used by Jersey. If the length of the content is known up front then Jersey uses a fixed streaming mode of content transfer. If, however, the length is not known ahead of time then Jersey defaults to a non-streaming mode. This non-streaming mode copies data temporarily into a ByteArrayInputStream and for larger content this process creates a lot of temporary short-lived objects. If the initial memory allocation is too low then the result is a significant stress on the garbage collector. In my case of MIME multipart transfer the length is not known up front and therefore content transfer occurs in non-streaming mode and causes the problem to exhibit itself.
Just performing non-streaming HTTP transfer isn’t enough to make the problem obvious though. If the initial memory allocation is too small then performance is mediocre but not horrible. It isn’t until I bring JAXB into the picture that things get really bad. What I discovered is that the simple initialization of a JAXB context consumes some area of Java memory such that programs with lots of short-lived objects start performing really badly. You don’t even need to use JAXB ever again. Just simply create a JAXB context and then never use it and you will see things slow down. I am not exactly sure why this happens. Since the JAXB context is a long-lived object it is not clear to me how it so adversely affects the behavior of short-lived objects. The effect is dramatic though as you can see from the test program.
The moral is: if you use JAXB you better make sure to provide the JVM with a larger initial memory allocation (-Xms) or else your program will slow down dramatically if it creates lots of short-lived objects.