How can I get the memory that my Java program uses via Java's Runtime API?
Asked Answered
M

6

69

There are similar questions out there, but they seem to avoid answering this specific question. How can I get the memory that my Java program uses via Java's Runtime API?

The answer here indicates that I can do something like this:

System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);

But this always returns the same number, no matter which program I run. For example, below I have a program where no matter how many numbers I put in the map, the memory usage stays the same.

package memoryTest;

import java.util.HashMap;
import java.util.Map;

public class MemoryTest {

    static Map<Integer, NewObject> map = new HashMap<Integer, NewObject>();

    public static void main(String[] args){

        System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);
        fillMemory(25);

        System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);
    }

    static int j=0;
    public static void fillMemory(int i){

        for(int k=0; k< 2000; k++)
            map.put(j++, new NewObject());

    }
    

    public static class NewObject{
        long i = 0L;
        long j = 0L;
        long k = 0L;
    }
    
}

via cambecc's main method, output is:

# 3085, Total: 128516096, Free: 127173744, Diff: 671120
# 173579, Total: 128516096, Free: 110033976, Diff: 671128
# 335207, Total: 128516096, Free: 92417792, Diff: 637544
# 672788, Total: 224198656, Free: 159302960, Diff: 1221520
# 1171480, Total: 224198656, Free: 106939136, Diff: 1221544
# 1489771, Total: 368377856, Free: 227374816, Diff: 1212984
# 1998743, Total: 368377856, Free: 182494408, Diff: 1212984
Merganser answered 28/6, 2013 at 22:44 Comment(17)
getRuntime().totalMemory() refers to the total amount of memory available to the Java runtime, not the amount of memory available to your application in particular.Lichen
@Robert Harvey I have edited. The answer provided isn't working for me for some reason...Merganser
I've reopened, but maybe you misunderstood me. getRuntime().totalMemory isn't going to work for what you want to do.Lichen
Thanks. Yes, I understand. The answer that I referenced above seems to indicate it would.Merganser
System.out.println("Used Memory : " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) + " bytes"); -- although that may return used memory by all java processes.Lichen
possible duplicate of What is the exact meaning of Runtime.getRuntime().totalMemory() and freeMemory()?Bealle
@Raedwald: Yeah, we know. We actually need a solution that works here.Lichen
@RobertHarvey The new program I added above tests this, using what should be a varying amount of memory, but the result, no matter the argument is always the same.Merganser
https://mcmap.net/q/281784/-during-execution-how-can-a-java-program-tell-how-much-memory-it-is-usingLichen
Thanks. I really think this is basically the same answer as the one I reference above. No matter. To anyone else, you can run my program above to see that no matter how much you fill the map the memory stays the same. Is there any way to truly see how much memory a program consumes?Merganser
@DanBray Did you read the question first?Merganser
@MattB I reread your question but I could not think of a solution. I've found an article that might help. Have a look at my answerHoward
It's not clear to me why this is not a duplicate question.Bealle
@Raedwald: Look at the question titles.Lichen
@MattB please see my answer below. Your approach to measuring memory usage is correct, but you need to make note of the difference between the resolution and precision of freeMemory.Vanhook
@RobertHarvey: To clarify, why do you say totalMemory is not going to work for @MattB? Looking at this question as it is currently phrased, @MattB wants to measure the memory use of his Java program running on a single JVM instance. For this scenario, totalMemory - freeMemory is correct. The results of totalMemory are not affected by other JVM instances (i.e., processes) running on the same machine. And @MattB makes no mention of other programs running inside the same JVM instance as his program. How is totalMemory not correct?Vanhook
@cambecc: You'd have to ask MattB about that.Lichen
V
113

You're doing it correctly. The way to get memory usage is exactly as you described:

Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()

But the reason your program always returns the same memory usage is because you are not creating enough objects to overcome the precision limitations of the freeMemory method. Although it has byte resolution, there is no guarantee for how precise freeMemory needs to be. The javadoc says as much:

an approximation to the total amount of memory currently available for future allocated objects, measured in bytes.

Try the following, which creates two million NewObject instances, and prints out each time the result of freeMemory changes:

public static void main(String[] args) {
    Runtime rt = Runtime.getRuntime();
    long prevTotal = 0;
    long prevFree = rt.freeMemory();

    for (int i = 0; i < 2_000_000; i++) {
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        if (total != prevTotal || free != prevFree) {
            System.out.println(
                String.format("#%s, Total: %s, Free: %s, Diff: %s",
                    i, 
                    total,
                    free,
                    prevFree - free));
            prevTotal = total;
            prevFree = free;
        }
        map.put(i, new NewObject());
    }
}

On my machine, I see output like the following

#0, Total: 513998848, Free: 508635256, Diff: 0
#21437, Total: 513998848, Free: 505953496, Diff: 2681760
#48905, Total: 513998848, Free: 503271728, Diff: 2681768
#73394, Total: 513998848, Free: 500589960, Diff: 2681768
#103841, Total: 513998848, Free: 497908192, Diff: 2681768
...

Notice how the reported free memory did not change until the 21,437th object was instantiated? The numbers suggest freeMemory for the JVM I'm using (Java7 Win 64-bit) has a precision of just over 2.5MB (although if you run the experiment, you'll see this number varies).

-- Edit --

This code is the same as above, but prints more details about memory usage. Hopefully it's a bit clearer how the JVM's memory usage behaves. We continuously allocate new objects in a loop. During each iteration, if the totalMemory or freeMemory is the same as the last iteration, we don't print anything. But if either has changed, we report current memory usage. The values represent the difference between current usage and the previous memory report.

public static void main(String[] args) {
    Runtime rt = Runtime.getRuntime();
    long prevTotal = 0;
    long prevFree = rt.freeMemory();

    for (int i = 0; i < 2_000_000; i++) {
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        if (total != prevTotal || free != prevFree) {
            long used = total - free;
            long prevUsed = (prevTotal - prevFree);
            System.out.println(
                "#" + i +
                ", Total: " + total +
                ", Used: " + used +
                ", ∆Used: " + (used - prevUsed) +
                ", Free: " + free +
                ", ∆Free: " + (free - prevFree));
            prevTotal = total;
            prevFree = free;
        }
        map.put(i, new NewObject());
    }
}

On my notebook, I see the following output. Note your results will differ depending on OS, hardware, JVM implementation, etc.:

#0, Total: 83427328, Used: 1741048, ∆Used: 83427328, Free: 81686280, ∆Free: 0
#3228, Total: 83427328, Used: 1741080, ∆Used: 32, Free: 81686248, ∆Free: -32
#3229, Total: 83427328, Used: 2176280, ∆Used: 435200, Free: 81251048, ∆Free: -435200
#7777, Total: 83427328, Used: 2176312, ∆Used: 32, Free: 81251016, ∆Free: -32
#7778, Total: 83427328, Used: 2611536, ∆Used: 435224, Free: 80815792, ∆Free: -435224
...
#415056, Total: 83427328, Used: 41517072, ∆Used: 407920, Free: 41910256, ∆Free: -407920
#419680, Total: 145358848, Used: 39477560, ∆Used: -2039512, Free: 105881288, ∆Free: 63971032
#419681, Total: 145358848, Used: 40283832, ∆Used: 806272, Free: 105075016, ∆Free: -806272
...

There are a few observations from this data:

  1. Used memory tends to increase, as expected. Used memory includes live objects and garbage.
  2. But used memory decreases during a GC, because garbage has been discarded. For example, this occurred at #419680.
  3. The amount of free memory reduces in chunks, not byte-by-byte. The chunks vary in size. Sometimes the chunks are really tiny, like 32 bytes, but usually they are larger, like 400K, or 800K. So it appears the chunk size will vary a fair bit. But compared to total heap size, the variation appears tiny. For example, at #419681 the chunk size is only 0.6% of the total heap size.
  4. Free memory tends to decrease, as expected, until a GC kicks in and cleans up garbage. When this occurs, free memory increases pretty dramatically, depending on the amount of discarded garbage.
  5. This test generates a lot of garbage. As the hashmap grows in size, it rehashes its contents, thus generating a lot of garbage.
Vanhook answered 29/6, 2013 at 5:2 Comment(11)
Thank you for your response cambecc. This gives me some insight to my problem, although if I add, for example, more fields to NewObject class (such as more NewObject) I should be able to see a memory increase with my program, but I get a stack overflow instead. Running your main method, I get the following, which appears to be pretty unreliable, as it fluctuates from more to less to more again... (I've added to the main questions, since formatting is messy for a comment)Merganser
Also, why doesn't diff change (in yours, and part of my orig question)? I thought that was the point.Merganser
You're witnessing the garbage collector at work (not sure why you get a stack overflow tho--sounds like a bug in your test). The JVM's heap starts out empty with a small size (128MB). As more objects get created, the heap gets more full. Eventually the heap becomes so full that it triggers a full GC, causing two effects: 1) the heap size is increased to 224MB, thus increasing totalMemory, and 2) garbage is collected, increasing freeMemory. All very normal. And it appears your JVM changes its precision of freeMemory depending on... who knows. Again the behavior is within spec.Vanhook
BTW, Diff is not the amount of used memory. It is a measurement of the precision of freeMemory, i.e., the difference between the previously reported amount of freeMemory and the current reported amount. You would expect it to remain fairly constant because it would be odd for the precision of freeMemory to change chaotically.Vanhook
This image is a perfect example of how your heap is behaving: i.sstatic.net/GGXiQ.png. The amount of orange on the screen is freeMemory, and amount of blue is used memory, and the top orange line represents totalMemory. Notice how free memory tends to increase (i.e., used memory decreases) when the heap grows in size? This isn't a hard and fast rule. In my experience, the GC's behavior is often mysteriously complex.Vanhook
@MattB Does that make sense? Do you need clarification on anything else?Vanhook
Sry the delayed response. I've been thinking about the problem. So basically totalmem - freemem isn't going to help me see how much mem my prog is using. With that said, I think you are contradicting your orig post a bit with these last few comments. You say the reason my diff # wasn't changing at first was I wasn't creating enough objects, but then claimed that it should remain ~constant. My understanding is a bit better than before, and I think I know what you were trying to say, so I'll check this as the correct answer... but mybe clarifying that for others would be good. Thanks again.Merganser
Hi @MattB: I've edited my answer to provide more details. Please review. Also, freeMemory will help you see how much memory your program uses, but you need to keep the following in mind: 1) your program will constantly generate garbage, and until a GC occurs, this garbage counts as used memory. 2) The precision of freeMemory does not provide byte-for-byte accuracy. In fact, the amount of precision seems to vary a fair bit (I was wrong about it being roughly constant). However, the amount it varies by is usually pretty small compared to the total heap size.Vanhook
One final point of clarification, "You say the reason my diff # wasn't changing at first was I wasn't creating enough objects, but then claimed that it should remain ~constant". Actually, what I was saying was the reason why your used memory number wasn't changing at first was you weren't creating enough objects. And once you started creating enough objects, the amount of free memory should reduce in chunks, where chunk size == Diff amount. I expected the chunk size to remain fairly constant, but I was wrong about that. Seems to vary a fair bit.Vanhook
You just blew my mind with the 2_000_000, this is the first time i've seen the _ feature used and didn't know it was a thingNazi
> the GC's behavior is often mysteriously complex and it is evolving over time!Shammy
D
4

I've got the following methods

public static long getMaxMemory() {
    return Runtime.getRuntime().maxMemory();
}

public static long getUsedMemory() {
    return getMaxMemory() - getFreeMemory();
}

public static long getTotalMemory() {
    return Runtime.getRuntime().totalMemory();
}

public static long getFreeMemory() {
    return Runtime.getRuntime().freeMemory();
}

which return the (used) Memory in bytes.

If you want to recalculate to MiB I've got:

private static final long MEGABYTE_FACTOR = 1024L * 1024L;
private static final DecimalFormat ROUNDED_DOUBLE_DECIMALFORMAT;
private static final String MIB = "MiB";

static {
    DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.ENGLISH);
    otherSymbols.setDecimalSeparator('.');
    otherSymbols.setGroupingSeparator(',');
    ROUNDED_DOUBLE_DECIMALFORMAT = new DecimalFormat("####0.00", otherSymbols);
    ROUNDED_DOUBLE_DECIMALFORMAT.setGroupingUsed(false);
}


    public static String getTotalMemoryInMiB() {
        double totalMiB = bytesToMiB(getTotalMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(totalMiB), MIB);
    }

    public static String getFreeMemoryInMiB() {
        double freeMiB = bytesToMiB(getFreeMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(freeMiB), MIB);
    }

    public static String getUsedMemoryInMiB() {
        double usedMiB = bytesToMiB(getUsedMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(usedMiB), MIB);
    }

    public static String getMaxMemoryInMiB() {
        double maxMiB = bytesToMiB(getMaxMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(maxMiB), MIB);
    }

    public static double getPercentageUsed() {
        return ((double) getUsedMemory() / getMaxMemory()) * 100;
    }

    public static String getPercentageUsedFormatted() {
        double usedPercentage = getPercentageUsed();
        return ROUNDED_DOUBLE_DECIMALFORMAT.format(usedPercentage) + "%";
    }
Detailed answered 26/3, 2018 at 9:47 Comment(1)
This answer is incomplete; see his answer here https://mcmap.net/q/281785/-get-real-ram-usage-of-current-jvm-with-javaShammy
W
2
public static void memoryStats() {
    int mb = 1024 * 1024;
    // get Runtime instance
    Runtime instance = Runtime.getRuntime();
    System.out.println("***** Heap utilization statistics [MB] *****\n");
    // available memory
    System.out.println("Total Memory: " + instance.totalMemory() / mb);
    // free memory
    System.out.println("Free Memory: " + instance.freeMemory() / mb);
    // used memory
    System.out.println("Used Memory: "
            + (instance.totalMemory() - instance.freeMemory()) / mb);
    // Maximum available memory
    System.out.println("Max Memory: " + instance.maxMemory() / mb);
}

REF: Here

Westernmost answered 13/6, 2020 at 14:5 Comment(0)
A
0

Following can be used in your java method to get the memory related statistics.

// Get the Java runtime
Runtime runtime = Runtime.getRuntime();
// Run the garbage collector
runtime.gc();
// Calculate the used memory
long memory = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Used memory is bytes: " + memory);
System.out.println("Used memory is megabytes: "
+ bytesToMegabytes(memory));
Abstinence answered 1/10, 2019 at 6:1 Comment(1)
runtime.gc(); is almost unnecessary. It just hints at the JRE to start GC. But should GC be started is up to JRE.Nanine
E
0

Fair free memory:

maxMemory() - totalMemory() + freeMemory()

and it will be the same as:

maxMemory() - (used memory), where (used memory) = totalMemory() - freeMemory()

because the freeMemory() gives you only the free available memory inside the totalMemory(), but the totalMemory() still can grow up to the maxMemory().

Esparza answered 1/7, 2021 at 8:13 Comment(0)
A
0
public class MemoryUtil {
    
    public static void print() {
        Runtime runtime = Runtime.getRuntime();

        long allocated = runtime.totalMemory();
        long used = allocated - runtime.freeMemory();
        long max = runtime.maxMemory();

        long available = max - used;

        log.info(String.format("Max: %d Allocated: %d Used: %d Available: %d",
                toMb(max),
                toMb(allocated),
                toMb(used),
                toMb(available)));
    }

    private static long toMb(long bytes) {
        return bytes / 1_000_000;
    }
}

Allocated can go up to Max.

If Available gets close to 0, application will become unresponsive or may throw an OutOfMemoryError.

Amalee answered 22/2 at 4:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.