最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Why does Runtime.maxMemory() change its value on separate calls? - Stack Overflow

programmeradmin4浏览0评论

I was debugging some OutOfMemoryError issues in my code, so I built a method that printed out the RAM statistics, including the max memory.

To my surprise, multiple attempts at calling the method returned different values for the MAX ram.

I set my ram properties on my start command. Here is the snippet that is relevant.

... -Xmx=$SOME_VAR -Xms=$SOME_VAR

Because of this snippet, the output from Runtime.totalMemory() ends up being the exact same as the output from Runtime.maxMemory().

How is it that the output from Runtime.maxMemory() constantly changes? It's, admittedly, not too much in most cases. Maybe half of a gigabyte. But sometimes, those half gigs can add up to almost 3 gigabytes of difference from when we started.

So I just wanted to know -- why does the output of Runtime.maxMemory() change over time? It's not like the hardware gained or lost RAM, right?

And to give some relevant details, my system has 32 gigabytes of RAM, my SOME_VAR above evaluates to roughly 25 gigabytes, and right before I run Java, my system has roughly 29 gigabytes free.

And to be clear, when I say I get different values, I mean that the following code has the possibility to print out different values.

for (int i = 0; i < 10; i++) {
    Thread.sleep(10_000);
    System.out.println(Runtime.getRuntime().maxMemory());
}

Sometimes, I get 23.5, othertimes, I get 24, etc.

Here is some more info that might help.

  • Java version -- OpenJDK 1.8 392
  • OS -- AWS RHEL Linux OS
  • Hardware -- AWS EC2 r5.xlarge instance
  • RAM -- 32 GB
  • -Xmx -- 25 GB, but we are experimenting with different values

I was debugging some OutOfMemoryError issues in my code, so I built a method that printed out the RAM statistics, including the max memory.

To my surprise, multiple attempts at calling the method returned different values for the MAX ram.

I set my ram properties on my start command. Here is the snippet that is relevant.

... -Xmx=$SOME_VAR -Xms=$SOME_VAR

Because of this snippet, the output from Runtime.totalMemory() ends up being the exact same as the output from Runtime.maxMemory().

How is it that the output from Runtime.maxMemory() constantly changes? It's, admittedly, not too much in most cases. Maybe half of a gigabyte. But sometimes, those half gigs can add up to almost 3 gigabytes of difference from when we started.

So I just wanted to know -- why does the output of Runtime.maxMemory() change over time? It's not like the hardware gained or lost RAM, right?

And to give some relevant details, my system has 32 gigabytes of RAM, my SOME_VAR above evaluates to roughly 25 gigabytes, and right before I run Java, my system has roughly 29 gigabytes free.

And to be clear, when I say I get different values, I mean that the following code has the possibility to print out different values.

for (int i = 0; i < 10; i++) {
    Thread.sleep(10_000);
    System.out.println(Runtime.getRuntime().maxMemory());
}

Sometimes, I get 23.5, othertimes, I get 24, etc.

Here is some more info that might help.

  • Java version -- OpenJDK 1.8 392
  • OS -- AWS RHEL Linux OS
  • Hardware -- AWS EC2 r5.xlarge instance
  • RAM -- 32 GB
  • -Xmx -- 25 GB, but we are experimenting with different values
Share Improve this question edited Feb 15 at 18:01 davidalayachew asked Feb 5 at 4:24 davidalayachewdavidalayachew 1,0453 gold badges16 silver badges38 bronze badges 9
  • The first thought is "just because you've set max memory doesn't mean your system has that much memory, so is java simply reporting different values because each time you run it it's simply asking for as much as it can?". How much memory does your system have, what's the SOME_VAR env var, and how much memory do you have free when you run java? (and remember to update your post with those details) – Mike 'Pomax' Kamermans Commented Feb 8 at 18:23
  • @Mike'Pomax'Kamermans My system has 32 gigabytes, and SOME_VAR evaluates to roughly 25 gigabytes. Right before I run Java, I have roughly 28-29 gigabyte free. And to be clear, I get different values on the same run. So, I could do a Thread.sleep() for 30 secs, and then get a value that differs by almost a gigabyte. – davidalayachew Commented Feb 8 at 18:36
  • @Mike'Pomax'Kamermans Edited my post – davidalayachew Commented Feb 8 at 18:43
  • I got it right: you set the heap size to aroung 25GB, the has 32 GB, and after you started the JVM, the OS still reports 29 GB as free? – tquadrat Commented Feb 8 at 18:45
  • 2 @ozkanpakdil I don't see how your link addresses my question because I don't see where your link explains why multiple calls to the same method during a single run of the JVM would produce different answers. Your linked answer explains why multiple SEPARATE runs of the JVM might produce separate answers, as well as how to predict them. But that is not my problem. – davidalayachew Commented Feb 11 at 20:25
 |  Show 4 more comments

2 Answers 2

Reset to default 4 +400

Runtime.getRuntime().maxMemory() is entirely dependent on the type of garbage collector currently in use. The Parallel collector is the default on OpenJDK 1.8, and it attempts to minimise the heap size as long as the application is meeting certain latency/throughput limits.

Some brief background: the application running inside the JVM has its memory space, the heap, segmented from the rest of the memory used by the JVM (which contains the call stack, class metadata, etc., but isn't otherwise relevant here). -Xmx sets the maximum allowed heap size, which the JVM immediately reserves (requests from the OS), and -Xms sets the initial heap size, which the JVM immediately commits (allocates).

Runtime.getRuntime().maxMemory() is an (optimistic) representation of the maximum usable heap size: it tells you how many bytes the JVM 'believes' could be filled with Java objects at this exact moment in time (including bytes occupied by objects that already exist). Consequently, the result isn't directly affected by allocations and it doesn't necessarily reflect your hardware - you can pass -Xmx1T to the JVM and maxMemory() will report 1TB regardless of how much RAM you have. Here's the JVM call itself (with some boilerplate removed):

jlong JVM_MaxMemory(void) {
  return Universe::heap()->max_capacity();
}

For most GC implementations, their max capacity is just the maximum heap size. However, ParallelGC's is different because it maintains two "survivor" spaces (heap areas that contain relatively new objects). At all times, one of the survivor regions contains objects and the other one is empty, and this alternates every GC cycle: any live objects in the 'occupied' region are moved to the previously 'empty' one.

This explains why maxMemory() under e.g. G1GC will be identical to the value specified via -Xmx, but under ParallelGC it will be a little less - the max heap space is reduced because objects can't ever exist in the empty survivor space (and so part of the heap is, essentially, wasted). You can reduce the survivor space to save memory, but that can lead to more frequent, more expensive garbage collecting. A larger survivor space can mean less time spent GCing (it's complicated), but will waste more heap space.

The Parallel GC attempts to balance those trade-offs with its "AdaptiveSizePolicy", which monitors the GC performance and automatically adjusts the heap partitions (e.g. the survivor spaces) in order to meet specific latency and throughput goals. If/while those are met, the goal becomes heap size minimisation - and this goal, unlike other GCs, ignores -Xms.

So, Runtime.getRuntime().maxMemory() is really what the garbage collector currently 'thinks' the maximum usable heap size is, and ParallelGC aims to reduce the heap footprint regardless of the minimum size you set.

Runtime.maxMemory() in Java returns the maximum amount of memory that the Java Virtual Machine (JVM) may use for heap allocation. Some JVMs (like Android’s ART or JIT-enabled JVMs) may dynamically adjust heap size based on usage patterns. Since system conditions are not exactly the same each time, the maximum memory limit can change depending on memory pressure.

By the way, if your application or the system triggers a manual garbage collection, the JVM may adjust its memory limits dynamically.

Since you're running this on an AWS EC2 instance, memory limits can be dynamically adjusted by AWS due to hypervisor behavior and containerization (cgroups). EC2 instances do not necessarily guarantee a static amount of RAM at all times. The JVM queries available memory from the OS, and if AWS's hypervisor reallocates memory due to other workloads, the JVM might see a different max memory value.

发布评论

评论列表(0)

  1. 暂无评论