在Java 7之前,JVM内存中有一个名为PermGen的区域,JVM用于保存其类.在Java 8中,它被删除并被称为Metaspace的区域取代.
PermGen和Metaspace之间最重要的区别是什么?
我知道的唯一区别是java.lang.OutOfMemoryError: PermGen space
不能再抛出并MaxPermSize
忽略VM参数.
我有一个带有一些有趣行为的J2EE应用程序......堆似乎表现良好,随着时间的推移随着垃圾收集而增长和缩小.没有明显的整体长期堆扩展.然而,在我们遇到MaxMetaspace并遇到OOME之前,元空间一直稳定地以每小时20 Mb的速度增长.我尝试了并行和G1垃圾收集器(jdk1.8.0_40).
应用程序在执行期间没有重新部署,因此它似乎不是典型的类加载器泄漏.有没有人建议如何追查这个泄漏的来源?
在将我们的Java应用程序(在Tomcat上运行的服务)JRE从Java 7切换到Java 8后,我们开始看到java.lang.OutOfMemoryError: Metaspace
运行了几天后流量很大.
堆使用没问题.在性能测试期间执行相同的代码流之后,元空间会跳转.
可能导致元空间内存问题的原因是什么?
目前的设置是:
-server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=3200m -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=1000
-XX:+DisableExplicitGC -XX:+PrintGCDetails
-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=7 -XX:NewSize=5004m
-XX:MaxNewSize=5004m -XX:MaxTenuringThreshold=12
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintFlagsFinal
-XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution
-XX:+PrintGCCause -XX:+PrintAdaptiveSizePolicy
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=3 -XX:GCLogFileSize=200M
Run Code Online (Sandbox Code Playgroud)
该应用程序也大量使用反射.我们还使用自定义类加载器.所有这些都在java 7中运行良好.
在我们在Jdk 8上运行的应用程序中,我们使用VisualVM来跟踪加载类的使用情况和元空间的使用情况.
在我们的应用程序运行的某个时间点,我们看到加载的类的数量不再增加,但是在我们的程序运行时,元空间的大小仍然增加.那么除了类之外还有哪些东西存储在元空间中,这可能会导致这种情况呢?
在Java 8堆打印输出中,您可能会看到如下所示的行:
Metaspace 使用 2425K,容量 4498K,承诺 4864K,保留 1056768K
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html尝试解释该行:
在以Metaspace开头的行中,使用的值是用于加载类的空间量.该容量值可用于当前分配块的元数据的空间.该承诺值的可用空间块的数量.的保留值是用于元数据的空间保留(但不一定提交)的量.
再次,从上面的链接:
从操作系统请求空间,然后分成块.类加载器从其块中为元数据分配空间(块绑定到特定的类加载器).
我想知道每个字段的含义(使用,容量,承诺,保留),但我很难理解上面的定义.
我的理解是,元空间是从JVM进程的虚拟地址空间中划分出来的.JVM在启动时保留初始大小,基于-XX:MetaspaceSize,它具有未记录的,特定于平台的默认值.我假设保留是指元空间的总大小.空间分为块.我不确定每个块是否具有相同的大小.每个块包含与单个类加载器关联的类元数据.
容量和承诺的声音对我来说就像是免费空间(基于链接的定义).由于元数据存储在块中,因此我假设使用的+容量将等于已提交,但事实并非如此.也许承诺意味着使用的保留空间,但那么使用的意思是什么?元数据使用的空间?那么,还有什么方法可以使用这个空间?
我希望你看到我的困惑.我希望澄清这些定义.
我有这个代码动态生成类并加载它
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我针对Java 7(jdk1.7.0_60)启动了这个类,正如预期的那样,它填满了PermGenSpace并且堆仍未使用
图像显示permgen使用超时,最后JVM终止
现在相同的代码针对Java 8(jdk1.8.0_40-ea)运行并且正如预期的那样它继续扩展本机内存(Metaspace)但令人惊讶的是,1g的Metaspace它在OldGen中消耗了3g的堆(随着时间的推移,几乎是Metaspace的3倍)
该图显示了Metaspace使用超时和系统内存使用示例
这封来自Jon Masamitsu的电子邮件和这张JEP门票说
interned
String
和Class stats以及一些misc数据已移至Heap
当它将更多类加载到Metaspace中时,究竟是什么导致了堆的增加?
是否有任何工具能够从Java8热点vm获取Metaspace转储?
Java 8使用能够动态扩展的元空间.GC将在满足空间运行时运行.这是否意味着GC永远不会在元空间上运行?
我的Java 8应用程序使用了大量内存.我想知道运行时我的元空间大小是多少.我怎么做?
我正在考虑设置maxMetaSpace.我该怎么做呢?有什么建议?
我正在做一些测试,以了解元空间内存(Java 8以上)的工作原理.当我动态创建100,000个类时,元空间内存正在增长(显然),但堆内存也在增长.有人可以向我解释为什么会这样吗?
PS:我正在使用128 MB的堆和128 MB的元空间运行测试.
@Test
public void metaspaceTest() throws CannotCompileException, InterruptedException {
ClassPool cp = ClassPool.getDefault();
System.out.println("started");
for (int i = 0; i <= 100000; i++) {
Class c = cp.makeClass("br.com.test.GeneratedClass" + i).toClass();
Thread.sleep(1);
if (i % 10000 == 0) {
System.out.println(i);
}
}
System.out.println("finished");
}
Run Code Online (Sandbox Code Playgroud)
见下图:
我正在运行的java进程在运行的第一个小时左右执行得很好.但是,性能会迅速降低.在进行性能分析时,我发现元空间垃圾收集经常发生,直到小时标记,然后失控:
我很确定我能够使用-XX:MaxMetaspaceSize选项来解决这个问题.但是,我想更多地了解为什么会出现这种行为.我无法想象为什么垃圾收集算法的行为会像这样.有没有人对更好的解决方案有解释或建议?谢谢
metaspace ×10
java ×7
java-8 ×7
jvm ×5
permgen ×2
classloader ×1
dump ×1
java-7 ×1
jvm-hotspot ×1
linux ×1
memory-leaks ×1