It turns out that using ResultSet.TYPE_SCROLL_INSENSITIVE or ResultSet.TYPE_FORWARD_ONLY on a java.sql.ResultSet can potentially give you a vastly different memory footprint. For example, we discovered today that a process pulling 42,000 rows out of a database and converting them into objects might take 70MB to do its job, or 800MB to do its job, depending on which type you use (at least with a Sybase jconn2 JDBC driver).
That get your interest? Read on to see my observations and my wild-ass guess at why.
Again profiling with JProfiler (really, you should not be doing performance analysis without a profiler or you will never find these kinds of things) I discovered that, in particular, ResultSet.getInt() using jconn2 is an incredible memory pig. It uses 7 times as much memory in temporary objects as calls to getDate(), getString() and getDouble(). I’m talking about objects that is uses in the process of building the “int” it returns to you, not the actual “int” (which is clearly very small). So, why do you care? If you’re using TYPE_FORWARD_ONLY you don’t have to care much unless you’re just trying to get the garbage collector to run less often. But if you’re using TYPE_SCROLL_INSENSITIVE (I can’t speak for TYPE_SCROLL_SENSITIVE because I didn’t test it) then you should care. A lot.
The reason is that the ability to scroll backwards and forwards with your result set doesn’t come free. It requires the result set to hold onto a lot more data internally instead of letting it go right way. So, consequently, with a forward only result set we were seeing the memory released every time we moved from one row to another, but with the scrolling result set it was holding onto all the memory until the very bitter end, when the result set was finally closed. For the application in question, that meant on our highest volume day of the year we simply couldn’t start. Even though the data we were trying to cache on startup only totalled 70MB or so in the end, it required (and held onto until the result set was closed) a whopping 800MB of memory to build that 70MB of data. That 800MB, when combined with the other data already in memory, meant we exceeded the 1.3GB limit for a JVM running on 32-bit Windows. We were stuck.
Credit for this last find goes to someone I work with named Shibu; without him I’d probably still be at work trying to figure out where those extra 730MB were coming from.