Has anyone else noticed that Garbage Collection appears to work differently if you explicitely call it versus if it self started via the GC background thread? I'm using JDK1.2.1_4 on Solaris and in searching for reason to explain our Servlet memory bloat found that calling GC() regularly results in better memory cleanup than letting GC() run on its own. To verify this I have a class with Implements runnable. The run() method simply creates a Vector of 1000 1K StringBuffers and then ends. In my main, I quickly create 1000 of the above threads taking just a 500 ms breather between thread create & runs. Then, on every 20th create/run I report on Runtime.totalMemory() and Runtime.freeMemory(). The memory usage by default is drastically higher than if I call Runtime.gc() after each Thread.run() or every 3rd Thread(), for example, than if I just the GC run automatically. It appears as the GC does a much deeper cleaning when asked to clean than if it was self initiated. Any thoughts on this? I've seen lots of posts on the net about Java Servlet memory bloat and I wonder if the assumption that auto-garbage collection does a thorough job automatically is getting us all?
Well, it makes sense that the maximum amount of memory used would be higher if you don't explicitly call GC, since the GC may not get around to running at all until it's forced to because you're nearly out of memory. However, I would have thought that once GC does run, it would do just as well when called for a memory shortage as when explicitly invoked. That is, I'd expect the memory after GC has run to be the same in either situation. But maybe not. Note that the API for System.gc() does say: <blockquote>When control returns from the method call, the Java Virtual Machine has made its best effort to recycle all discarded objects. </blockquote> (Emphasis mine.) So GC is supoosedly required to do the best it can in response to System.gc(). I don't believe there's any such requirement in the other situations where GC may occur - namely, when memory is nearly gone, or as a low-priority background activity. In the former case it's supposed to try to free at least enough memory to satisfy whatever allocation request has just been made, since OutOfMemoryError will occur otherwise. But there's not necessarily a need to keep GC'ing to collect all the eligible objects. And in the latter case, the JVM is supposed to try to be non-intrusive about its activities - meaning we prefer that "background" GC doesn't cause all other activity to grind to a halt once it starts (which earlier JDK's were prone to do). So it may often be better to quit early if there are other threads that need processor time. So in summary - it's news to me if it's true, but on reflection it doesn't seem unreasonable, IMO.
Firstly, I was wondering what language the JVM would have been written in. I guess it would be C/C++ or maybe a true compiling version of javac. I don't know, but if somone knows, I'd be interested in finding out. Secondly, assuming that the JVM is written in some sort of C derivative, I can understand this in the context of C programming for unix. when for instance the library function malloc() is called, it uses the system call sbrk() which extends the programs data segment by requesting more memory directly from the system. If this is called 100 times and then free() is called for the corresponding 100 chunks of memory, the size of the memory that was allocated by the 100 calls to malloc() is freed up for use again only by that program. ie, it isn't returned to the OS. It follows that there is no opposite to sbrk(), no way to return memory to the OS. So if you called malloc() 100 times and every 3 calls you invoked free() 3 times to free the memory in the last 3 calls, the program's data segment in the end would be much lower. Memory is only returned to the OS when the program ends. From your description this seems to me the way that the JVM is working. The memory usage only rises, but does not fall, even if the garbage collector runs. If the garbage collector runs frequently, the ceiling level of usage is lower than if it had run seldom.
No (to George), the JVM does not attempt to return memory to OS like that. Generally, the JVM retains the data segments and when they are cleared by the garbage collector, the data segments remain as heap space for the JVM to reuse. New objects will be allocated out of the available JVM heap space. Actually, some OS's will allow applications to return memory to the OS while the program is running, and there are some JVMs that claim to support this too. But back to the original question about the garbage collection working differently depending on it being explicitly vs. implictly started. This can be true, i.e. the garbage collection algorithm can be different for different situations. The garbage collection algorithm is not specified by the Java specification, and each JVM is at liberty to implement its own algorithm. Each newer version of JVM tends to use a more improved algorithm - the latest ones use generational scavenging. Bill Venners' book Inside the Java 2 Virtual Machine can give you more information if you are interested in garbage collection and JVM internals. In practice, the algorithm applied is pretty much the same independently of how it is triggered. The System.gc() call essentially sends a message to the garbage collector thread saying "now would be a good time to run a collection and, if you do, tell me when you're finished so I can return". The difference in your application behavior comes from you telling the garbage collector to try harder more often. Without your hints, the garbage collector tends to be lazy, sometimes running in idle time, but mainly not particularly trying hard until available memory gets low. And even when available memory gets low, the garbage collector may decide that the fastest way to provide more memory to the app is to request more from the OS rather than try to clear up as much garbage as possible. Memory management of Java apps can be confusing. There are two main aspects: firstly minimizing object creation (see the 'Garbage collection problems in java?' discussion thread in this forum); secondly tuning the size of the JVM heap, which you can specify with the -ms/-mx/-Xms/-Xmx options. This second tuning tends to be trial and error. Allowing the memory to get too large means garbage collection may be run less frequently, but will take more time when it is run. Limiting the memory to a smaller size can be better because the garbage collector is forced to run more often, which makes for smaller garbage collections with a lower impact on the app.