Jump to content
Excelsior Forums
MemoryHungry

Runtime.maxMemory() broken

Recommended Posts

Our application may use a lot of memory, and needs to detect and handle low memory situations. When using the JRE, we rely on Runtime.getRuntime().maxMemory() for an estimate of the max heap space, which works well. One main reason why we purchased Jet was the option to use adaptive memory, but we ran into a problem with our existing (multi-platform) code.

With Jet, maxMemory() always appears to return about 3 GB, even on a machine with substantially less memory and limited swap space (e.g. 700 MB RAM and 50 MB swap space). This is obviously a wrong answer, and leads to OutOfMemory errors. That leads to problems because they can appear in multiple threads; the worst problems occur when the errors are thrown during Thread creation and in static initializer code.

Could you fix this so that maxMemory() returns a more reasonable number? Of course, an estimate based on actual available memory (including swap space) would be best. But even something like:

min (3GB, built-in RAM + swap space) would give a more useful number.

A related issue is knowing how much build-in RAM the computer actually has. We could also use this as a guideline for warning the user about swap slowdowns. Is there a function or DLL where we could get the amount of built-in RAM?

Share this post


Link to post
Share on other sites

The Java SE API specification says:

public long maxMemory()

Returns the maximum amount of memory that the Java virtual machine will attempt to use

-------------

Note that it says nothing about the RAM capacity of the computer, the size of the swap file, available physical memory at the moment of invocation, etc.

From your description, I see that you try to use the return value to estimate max heap size. This is a misuse of the function because you rely on the implementation from Sun (which may change without a notice, not to mention other implementations) and disregard the Java SE API specification as the only valid reference.

-------------

Back to your issue.

This is obviously a wrong answer, and leads to OutOfMemory errors.

The value returned by maxMemory() cannot lead to OutOfMemory. The JET Runtime uses the value in the only way: if the amount of available (live) objects in the Java heap exceeds maxMemory(), OOM is thrown, however, it does not mean that allocation proceeds until 3GB is reached. The Runtime considers lots of other factors, such as available physical memory, to make decision about the time to garbage collect and the time to throw OOM.

For more details, please refer to the JET User's Guide, Chapter "Application considerations", section "Memory management"

---------

Finally, if you have any sample that shows that the application works ok with an explicit max heap setting whereas it throws OOM with the adative heap size, please send such an example to Excelsior Support (java at excelsior-usa.com) and we will look at it on our end.

Share this post


Link to post
Share on other sites
A related issue is knowing how much build-in RAM the computer actually has. We could also use this as a guideline for warning the user about swap slowdowns. Is there a function or DLL where we could get the amount of built-in RAM?

You may find these functions in the OS API. I'm not aware of any Java API which has such functionality.

Share this post


Link to post
Share on other sites

My issue is that maxMemory() (a) behaves differently than any Java 1.4-1.6 version on Windows and OS X, and (B) returns wrong values in Jet.

In Jet, maxMemory will return  3 GB, even if the application was compiled so throw OutOfMemoryErrors when "the Runtime exceeds the physical memory threshold" (cited form Jet help). When running on a machine with substantially less than 3 GB of RAM, the application will clearly NEVER try to use 3 GB of RAM. It will throw an OutOfMemoryError at about the time that heap size is equal to built-in RAM. Thus, maxMemory is clearly broken in Jet.

The real issue is that JVMs tend to hang or crash when memory gets really tight. Furthermore, JVMs will not start (or crash immediately) if the -Xmx parameter specified exceeds the available memory (or, more exactly, if no virtual memory block of the requested size is available).

With our scientific application that may consume memory rapidly, relying on OutOfMemoryErrors to indicate low-memory conditions was not sufficient. The problem is that the "worker" thread may consume available memory just before some other thread also needs memory - for example the event thread to update a progress indicator. OutOfMemoryErrors in other threads can lead to hangs, windows that remain open and cannot be closed, classes that cannot be loaded anymore (if the error happened in static initializer code), and crashes.

In both Sun and Apple Java, we had a workaround that used Runtime's memory functions to see when our application approaches the memory limits. It relied on maxMemory, which in all current Sun and Apple implementations from 1.4 to 1.6 returns the max heap size (although specific versions require minor workarounds for small accuracy issues). Jet's simplistic approach to always return 3GB obviously breaks this. Your response is not helpful (it does not even give the impression that you are trying to be helpful).

We have spent several days trying to work around this limitation in Jet, without success. In JUnit memory stress tests that work without problems on OS X and Windows with Sun JREs, we still get erratic behavior. I attached a screen shot that shows "extMalloc failed to allocate" error messages dumped to the console during one incident.

One possible solution to this problem would be as follows: when compiled with the "handling low memory" flag to not use virtual memory, maxMemory() should return the amount of currently available physical memory. When using virtual memory, the amount returned should be the amount of free virtual memory. The application obviously should not attempt to use more physical respectively virtual memory than is available, since such an attempt would certainly fail. Therefore,  these answers would make much more sense than always returning 3GB, as Jet does now.

post-2729-13028569903237_thumb.jpg

Share this post


Link to post
Share on other sites
maxMemory() should return the amount of currently available physical memory

On Windows, currently available physical memory may be low if much memory is spent by the system for the file cache.

If the application consumes the most of "available physical memory", the system discards (a part) of the cache and increases the available memory.

As a result,  the amount of available physical memory:

1) does not correlate with the RAM capacity of the machine

2) low available physical memory does not yet mean that OOM will be thrown soon

Furthermore, on (32-bit) Windows machines equipped with 2+ GB RAM, the system may fail to allocate memory due to lack of virtual address space not physical memory.The same is true for using virtual memory if the address space becomes the bottleneck.

It will not take long to change the calculation of maxMemory() but I cannot see how it helps you predict throwing OOM in your application.

JVM cannot emulate the behavior of operating system.

My issue is that maxMemory() (a) behaves differently than any Java 1.4-1.6 version on Windows and OS X, and (B) returns wrong values in Jet.

1) If you call it a bug , please refer to the standard (specification) not to an implementation. Otherwise, it is a possible improvement we are always ready to consider and discuss.

2) The Apple's implementation is a derivative from Sun JRE.

3) IBM's VM may have a different policy of setting maxMemory()

4) I will not be surprised if the Oracle's VM has yet another implementation.

5) A JVM cannot guarantee that in the range [totalMemory()...maxMemory()] OOM will never be thrown (it's a system dependent behavior). Not to mention the specification does not imply it.

6) You seem to rely on an implementation-dependent heuristics. BTW, did you ever try it under Linux?

----------------

I emphasize that we can implement something like min (3GB, built-in RAM + swap space) if you need but it will not guarantee flawless operation of memory-hungry applications. For instance, in all your reasoning you did not mention that the OS itself tends to occupy some amount of memory. It depends on a particular OS and even Windows flavors may vary in it. And what about other running processes which may be also very hungry for memory taking into account that swap space is a system-wide setting?

I understand your concern and if you believe it's helpful for your application, please,

place a feature request.

Share this post


Link to post
Share on other sites

I had just composed a rather lengthy answer, when the board logged me out automatically and all that work was lost. So this one will be shorter.

Bottom line:

When using automatic heap sizes, Jet's implementation of maxMemory() is useless. It always returns 3 GB, even though Java on common Win32 systems does not allow anywhere close to this much. The wrong "3 GB" answer will also be returned if there is absolutely no way that the heap could ever grow so big (for example on a machine with < 1 GB RAM, compiled to throw OutOfMemoryErrors when physical memory gets low. But Excelsior does not consider this as a bug.

Since our objective is to avoid application crashes and hangs, and our previous "heuristic" workaround that works perfectly fine on Sun JVMs relies on maxMemory(), we will have to add more heuristic workarounds, this time for the shortcomings of Jet. i Which means we will be tied to the current version for as long as possible, and have no incentive to extend the maintenance.

So I'll just leave a warning to other users who may have memory-hungry applications:

just like Sun and Apple JVMs, Jet-compiled applications can hang, misbehave, or crash when running out of memory. Since compiled Jet code seems to execute faster than normal Java, the likelihood of OutOfMemoryErrors in multiple threads (event thread, worker threads, timer threads, garbage collection, ...) very shortly after each other appears to be significantly increased. These multiple-thread OutOfMemoryErrors are the ones that can cause the most severe problems.

Existing workarounds for "fixed max size" heaps that use maxMemory will not work for Jet-compiled applications that use an adaptive heap size, since the value returned by Runtime.maxMemory is fixed and useless.

Share this post


Link to post
Share on other sites
I had just composed a rather lengthy answer, when the board logged me out automatically and all that work was lost.

The same happened when I wrote my previous post.  B)

The wrong "3 GB" answer will also be returned if there is absolutely no way that the heap could ever grow so big (for example on a machine with < 1 GB RAM

Not necessarily, if the system has enough virtual memory and address space, the heap can grow to this limit.

Clarification: in the current implementation, maxMemory() indicates the maximum heap size that cannot be exceeded, independently on the machine on which the app runs. If you specify the max heap size for your application (-Xmx), maxMemory() returns that value. If you enable the adaptive heap size mode, the value is set to the theoretical maximum for 32-bit systems (3GB for Windows, 4GB-4k for Linux). In addition, The Runtime tries to avoid page swap by collecting garbage when the available physical memory is really low but if the app performs brute force allocation, either OOM is thrown or virtual memory is used depending on the user option.

As Peter noted, on some machines OMM will be thrown earlier than the max heap size is actually reached.

The implementation can be tuned so that it returns some "more realistic" value but it will be just a hint. None can rely on the value to meet the condition

  [ totalMemory() < maxMemory() ]  implies OOM will not be thrown

So I'll just leave a warning to other users who may have memory-hungry applications: just like Sun and Apple JVMs, Jet-compiled applications can hang, misbehave, or crash when running out of memory.

Correct, the problem exists and it is general. It could be solved if JVMs could have more control over the allocation policy of the operating system.

Share this post


Link to post
Share on other sites

Not necessarily, if the system has enough virtual memory and address space, the heap can grow to this limit.

Your quote cuts off what I was talking about in a misleading way. I specifically stated "compiled to throw OutOfMemoryErrors when physical memory gets low". Your documentation indicates that virtual memory will not be used in such a situation (the two options are "use virtual memory" and "throw OutOfMemoryErrors").

Based on Jet documentation, I would expect that compiling an application with this flag will NEVER lead to a situation where the heap is larger than the actual physical memory (although I understand perfectly well that the OS will typically use virtual memory).

If this is a misunderstanding, could you clarify what exactly this compiler option does?

Share this post


Link to post
Share on other sites

Correct, the accurate statement would be

If the system has enough virtual memory and address space, the heap can potentially grow to the 3GB limit provided the "use virtual memory" option is switched on.

Based on Jet documentation, I would expect that compiling an application with this flag will NEVER lead to a situation where the heap is larger than the actual physical memory

Exactly. If you configure the JET Runtime to not use virtual memory, you may verify this behavior as follows.

1) Run your app

2) Open Windows Task Manager

3) Select the "Performance" tab

4) In the pane "Physical memory(K)", watch the number in the "Available" row

Note that under circumstances, the system will pump physical memory from "System Cache" to  "Available". However, it will never cross the boundary of the total available phys. memory.

Share this post


Link to post
Share on other sites

Which is great, because it allows us to at limit swapping to a reasonable amount. With unlimited swapping, the slowdowns can be extreme (and the users blame our application for being so slow).

But since the heap will never grow larger than the physical memory, min(3GB, amount of physical memory) would be a more reasonable answer than always returning 3GB if this compiler flag is set.

I saw Windows's behavior of actually using virtual memory to swap out (mostly) other processes, since it takes a while before the application actually get the entire physical memory space. But even with this limitation and other limitations discussed before, changing maxMemory to return the amount of physical memory if this compiler flag is set would be useful to detect when our application approaches the maximum heap space (we might actually warn the first time when approaching 75% or so). Most of our users are at universities and have to use old computers, so my guess is that at least 75% have 1 GB or less RAM installed.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×