0

I am seeing the below in the GC log before the JVM pauses for several minutes after which it gets restarted externally.

[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Metaspace (data) allocation failed for size 1075
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Usage:
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]   Non-class:    168.00 MB used.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]       Class:     19.79 MB used.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]        Both:    187.79 MB used.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Virtual space:
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]   Non-class space:      192.00 MB reserved,     170.06 MB ( 89%) committed,  3 nodes.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]       Class space:       96.00 MB reserved,      21.94 MB ( 23%) committed,  1 nodes.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]              Both:      288.00 MB reserved,     192.00 MB ( 67%) committed.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Chunk freelists:
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]    Non-Class:  6.83 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]        Class:  10.47 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]         Both:  17.30 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] MaxMetaspaceSize: 192.00 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] CompressedClassSpaceSize: 96.00 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Initial GC threshold: 21.00 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Current GC threshold: 192.00 MB
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] CDS: off
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] MetaspaceReclaimPolicy: balanced
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - commit_granule_bytes: 65536.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - commit_granule_words: 8192.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - virtual_space_node_default_size: 8388608.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - enlarge_chunks_in_place: 1.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - new_chunks_are_fully_committed: 0.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - uncommit_free_chunks: 1.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - use_allocation_guard: 0.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]  - handle_deallocations: 1.
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] Internal statistics:
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom]
[2025-07-18T18:08:42.605-0600][info][gc,metaspace,freelist,oom] num_allocs_failed_limit: 717.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_arena_births: 71282.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_arena_deaths: 63782.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_vsnodes_births: 4.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_vsnodes_deaths: 0.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_space_committed: 3072.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_space_uncommitted: 1.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_chunks_returned_to_freelist: 95133.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_chunks_taken_from_freelist: 114240.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_chunk_merges: 22894.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_chunk_splits: 33864.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_chunks_enlarged: 6650.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom] num_inconsistent_stats: 0.
[2025-07-18T18:08:42.606-0600][info][gc,metaspace,freelist,oom]

Looking at this log, it seems to point an imminent Metaspace OOM however I am not sure if its indeed the case given the log seems to indicate only an allocation failure & there was no java.lang.OutOfMemoryError: Metaspace getting thrown afterwards just a very long pause.

Is my understanding correct that the above implies an imminent Metaspace OOM?

8
  • 2
    I don't think anyone can give you a definitive answer on this. But what you are saying is plausible. Commented Jul 22 at 8:56
  • @StephenC Thank you…the reference to “1075” without a unit is a little odd..do you mind sharing any insights you may have? Commented Jul 22 at 10:20
  • 1
    The memory size in your log is pretty small, is this an embedded device? What garbage collector do you use, the standard G1GC? Commented Jul 22 at 11:29
  • 1
    I think it is 1075 "words". (Not sure what the "word" size is though. I can't find where the global BytesPerWord is assigned in the native code.) Commented Jul 22 at 15:47
  • 1
    For really deep questions ... it is best that you consult the OpenJDK source code. That's what I'm doing, albeit I'm not spending much time on it. Commented Jul 23 at 5:13

1 Answer 1

1

It's actually trying to recover, but it's killed before throwing the OOM exception, or finishing the recovery.

The metaspace is capped and cannot grow, so you have to increase your metaspace:

-XX:MetaspaceSize=256M

However, you need to monitor this, as it might be the case where the application is generating virtual classes and proxies, filling up the metaspace. GC will not cleanup the classes you loaded in your classloader so whenever you create something there, will stay there until the end. You need to unload it in order to recover that space, that is: to remove all references of that classloader and it's classes.

You can do this using java profilers like VisualVM.

LE: Just to clarify how things work.

GC will never cleanup classes loaded in the classloader. So when you create a proxy, it gets loaded in your classloader.

This code will prove that this is not cleaned up:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;

public class ClassLoaderExample {

    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        MemoryPoolMXBean metaspace = metaspace();

        System.out.println("Metaspace used: " + metaspace.getUsage().getUsed());

        for (int i = 0; ; i++) {
            Class<?> dynamicType = new ByteBuddy()
                    .subclass(Object.class)
                    .method(ElementMatchers.named("toString"))
                    .intercept(FixedValue.value("Hello World!"))
                    .make()
                    .load(ClassLoaderExample.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
                    .getLoaded();

            if (i % 100000 == 0) {
                System.out.println("Metaspace used: " + metaspace.getUsage().getUsed() + " of " + metaspace.getUsage().getCommitted());
                System.gc();
            }
        }

    }

    static MemoryPoolMXBean metaspace() {
        for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (pool.getName().contains("Metaspace")) {
                return pool;
            }
        }
        throw new RuntimeException("Metaspace not found");
    }

}

Will return OOM:

Metaspace used: 1197072
Metaspace used: 8398248 of 8650752
Metaspace used: 123650552 of 123994112
Metaspace used: 234890296 of 235274240
Metaspace used: 346141224 of 346554368
Metaspace used: 457403568 of 457768960
Metaspace used: 568629400 of 569049088
Metaspace used: 679867840 of 680329216
Metaspace used: 791102512 of 791543808
Metaspace used: 902339408 of 902823936
Metaspace used: 1013555440 of 1014104064
Metaspace used: 1124775896 of 1125253120
Metaspace used: 1236008680 of 1236533248
Metaspace used: 1347242088 of 1347878912
Metaspace used: 1458458368 of 1459027968
Metaspace used: 1569672832 of 1570308096
Metaspace used: 1680888168 of 1681522688
Metaspace used: 1792124488 of 1792737280
Metaspace used: 1903363368 of 1904017408
Metaspace used: 2014583800 of 2015297536
Metaspace used: 2125814184 of 2126446592
Metaspace used: 2237049112 of 2237726720
Exception in thread "main" java.lang.IllegalStateException: java.lang.OutOfMemoryError: Compressed class space
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$UsingUnsafeInjection.defineClass(ClassInjector.java:1093)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.injectRaw(ClassInjector.java:329)
    at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.injectRaw(ClassInjector.java:167)
    at net.bytebuddy.dynamic.loading.ClassInjector$AbstractBase.inject(ClassInjector.java:155)
    at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:241)
    at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:148)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6432)
    at com.example.ClassLoaderExample.main(ClassLoaderExample.java:27)
Caused by: java.lang.OutOfMemoryError: Compressed class space

This is the same with proxies, however the JVM also has a cache, so when you are using the same interface for proxy, it will not load a new proxy in the classloader.

Now there's a trick to it: short lived classloaders.

ByteBuddy for example uses a wrapper strategy by default: will create a new classloader and load the virtual class in that classloader instead of the provided one. This means that when the class is not referenced anymore, GC will clean it up because the class is not in the classloader, but in a short lived one, designed to keep only that virtual class.

If you change ClassLoadingStrategy.Default.INJECTION with ClassLoadingStrategy.Default.WRAPPER, the OOM will not happen, and you will see a drop in metaspace memory each time GC executes.

Bottom line:

  1. You might have an issue if you are generating classes / proxies at runtime. If you do this, monitor with jcmd or VisualVM. See where they come from. Optimize using short lived classloaders. However, caches can prevent the class of being unloaded: the cache holds reference to the object, the object hold reference to the class, the class references the classloader -> GC will not clean it up.

  2. You might have too many classes for the memory you currently have available. JVM will load your classes in a lazy way, whenever the class definition is needed. Classpath scanners, like the one from Spring Framework, can trigger the loading of multiple classes, even if you don't actively use them.

Sign up to request clarification or add additional context in comments.

6 Comments

Of course, the metaspace gets cleaned up. Don’t spread misinformation.
There's no misinformation, it gets cleaned up only if the classloader is no longer referenced. This is rarely the case, and if you don't reference the classes correctly, you end up with GC not being able to reclaim the space. So best to monitor and see if you have any class loader leaks. If your meta space is growing, then you might have a leak. Also, there are some things that can prevent class loader unloading, like static fields or caching, which retain references.
So now you acknowledge that the metaspace is cleanup up when the preconditions are fulfilled. So your answer’s statement “whenever you create something there, will stay there until the end” is blatantly false.
Ok, got your point. I meant to say that about the classes in the classloader. Rephrazed it. Also added a full explanation.
Thanks @PeterAdrian for your detailed post. I know I can increase Metaspace from the 192 MB that I had set, I was just trying to understand what's going on before I do that...as you mentioned I am also going to do the NMT..
As you note in the second point at the end, yes this is a spring app & I saw a recent addition of a dependency...since the event happened after several days of run I don't want to simply increase Metaspace..

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.