Java8 metaspace & heap usage
Asked Answered
C

2

16

I have this code to generate class dynamically and load it

import javassist.CannotCompileException;
import javassist.ClassPool;

public class PermGenLeak {
    private static final String PACKAGE_NAME = "com.jigarjoshi.permgenleak.";

    public static void main(String[] args) throws CannotCompileException, InterruptedException {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            ClassPool pool = ClassPool.getDefault();
            pool.makeClass(PACKAGE_NAME + i).toClass();
            Thread.sleep(3);
        }

    }
}

I launched this class against Java 7 (jdk1.7.0_60) and as expected it filled up PermGenSpace and heap remained unused Java 7 memory usage image shows permgen usage overtime and at the end JVM was terminated

Now the same code ran against Java 8 (jdk1.8.0_40-ea) and as expected it kept expanding native memory (Metaspace) but surprisingly for 1g of Metaspace it consumed 3g of Heap in OldGen (almost 3x of Metaspace maintained over time)

Java8 memory usage image shows Metaspace usage overtime and System memory usage sample

this email from Jon Masamitsu and this JEP ticket says

interned String and Class stats and some misc data has been moved to Heap

what exactly makes this increase in heap as it loads more classes into Metaspace ?

Copyist answered 19/10, 2014 at 21:12 Comment(0)
L
11

Run jmap -histo PID to see which objects consume the Heap Space.
When I ran your example I saw the heap full of Javassist auxiliary objects:

 num     #instances         #bytes  class name
----------------------------------------------
   1:        592309      312739152  [Ljavassist.bytecode.ConstInfo;
   2:       6515673      208501536  java.util.HashMap$Node
   3:       2964403      169188824  [C
   4:       1777622      102165184  [Ljava.lang.Object;
   5:       4146200       99508800  javassist.bytecode.Utf8Info
   6:       3553889       85293336  java.util.ArrayList
   7:       2964371       71144904  java.lang.String
   8:        593075       56944008  java.lang.Class
   9:        592332       47388032  [Ljava.util.HashMap$Node;
  10:        592309       37907776  javassist.bytecode.ClassFile
  11:        592308       37907712  javassist.CtNewClass
  12:       1185118       28555808  [B
  13:        592342       28432416  java.util.HashMap
  14:       1184624       28430976  javassist.bytecode.ClassInfo
  15:        592309       28430832  [[Ljavassist.bytecode.ConstInfo;
  16:        592322       23692880  javassist.bytecode.MethodInfo
  17:        592315       23692600  javassist.bytecode.CodeAttribute
  18:        592434       18957888  java.util.Hashtable$Entry
  19:        592309       18953888  javassist.bytecode.ConstPool
  20:        592308       18953856  java.lang.ref.WeakReference
  21:        592318       14215632  javassist.bytecode.MethodrefInfo
  22:        592318       14215632  javassist.bytecode.NameAndTypeInfo
  23:        592315       14215560  javassist.bytecode.ExceptionTable
  24:        592309       14215416  javassist.bytecode.LongVector
  25:        592309       14215416  javassist.bytecode.SourceFileAttribute
  26:        592507        9487584  [I
  27:             8        6292528  [Ljava.util.Hashtable$Entry;
  28:           212          18656  java.lang.reflect.Method
  29:           407          13024  java.util.concurrent.ConcurrentHashMap$Node
  30:           124           8928  java.lang.reflect.Field
Lumbering answered 19/10, 2014 at 22:51 Comment(1)
Thanks Andrei, I should have just executed this, I think I didn't reach at this scale in Java 7 example that is why I didn't see it in Java 7Copyist
A
3

what exactly makes this increase in heap as it loads more classes into Metaspace ?

My hypothesis is that this is "ordinary" garbage that is being created by your example. I surmise that:

  • The javaassist code creates regular heap objects. They are mostly "big" and that causes them to be allocated directly into the OldGen heap. Or something else causes that.

    (UPDATE - looking at @apangin's Answer, I now suspect that they started out in the YoungGen heap and were tenured ...)

  • When classLoader.defineClass is called under the hood, it creates objects in metaspace from the byte array containing the classfile.

  • The OldGen usage remains ... because nothing has triggered a full GC yet.

If you tweaked your example so that the classes were reachable, and then forced a full GC, I would expect (hope) to see the OldHeap usage to drop, indicating that it is "ordinary" garbage rather than a storage leak.

Astaire answered 19/10, 2014 at 22:50 Comment(2)
Thanks Stephen, It totally make sense now, I think I didn't reach at this scale of allocation in Java 7 example that is why I didn't see it in Java 7Copyist
can you please include code to demonstrate your solutionLashawn

© 2022 - 2024 — McMap. All rights reserved.