JVM之 方法区、永久代(PermGen space)、元空间(Metaspace)三者的区别

JVM之 方法区、永久代(PermGen space)、元空间(Metaspace)三者的区别文章目录0、前言(JVM运行时区域)1、PermGen(永久代)2、Metaspace(元空间)3、总结0、前言(JVM运行时区域)阅读此文章时,必须已经了解了jvm运行时数据区域。 根据JVM规范,JVM运行时区域大致分为方法区、堆、虚拟机栈、本地方法栈、程序计数器五个部分。1)、方法区方法区是JVM所有线程共享。主要用于存储类的信息、常量池、方法数据、方法代码等…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

0、前言(JVM 运行时区域)

阅读此文章时,必须已经了解了jvm 运行时数据区域。
 
在这里插入图片描述

根据 JVM 规范,JVM 运行时区域大致分为 方法区、堆、虚拟机栈、本地方法栈、程序计数器 五个部分。

1)、方法区
方法区是JVM 所有线程共享。
主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与 进行区分,通常又叫 非堆。 关于 方法区内存溢出 的问题会在下文中详细探讨。

2)、堆

堆内存也是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError 。

下面我们简单的模拟一个堆内存溢出的情况:

import java.util.ArrayList;
import java.util.List;
 
public class HeapOomMock { 
   
    
    List<byte[]> list = new ArrayList<byte[]>();
    int i = 0;
    
    public static void main(String[] args) { 
     
       
        boolean flag = true;
        while (flag){ 
   
            try { 
   
                i++;
                list.add(new byte[1024 * 1024]);//每次增加一个1M大小的数组对象
            }catch (Throwable e){ 
   
                e.printStackTrace();
                flag = false;
                System.out.println("count="+i);//记录运行的次数
            }
        }
    }
}

Jetbrains全家桶1年46,售后保障稳定

运行结果:

img

注意,这里我指定了堆内存的大小为16M,所以这个地方显示的count=14(这个数字不是固定的),至于为什么会是14或其他数字,需要根据 GC 日志来判断。

1、PermGen(永久代)

PermGen , 就是 PermGen space ,全称是 Permanent Generation space ,是指内存的永久保存区域。这块内存主要是被JVM存放Class和Meta信息的, Class 在被 Loader 时就会被放到 PermGen space 中。

绝大部分 Java 程序员应该都见过 java.lang.OutOfMemoryError: PermGen space 这个异常。
这里的 PermGen space 其实指的就是 方法区 。不过 方法区PermGen space又有一定的区别。

  • 方法区 是 JVM 的规范,所有虚拟机 必须遵守的。常见的JVM 虚拟机 Hotspot 、 JRockit(Oracle)、J9(IBM)

  • PermGen space 则是 HotSpot 虚拟机 基于 JVM 规范对 方法区 的一个落地实现, 并且只有 HotSpot 才有 PermGen space

    而如 JRockit(Oracle)、J9(IBM) 虚拟机有 方法区 ,但是就没有 PermGen space

    PermGen space 是 JDK7及之前, HotSpot 虚拟机 对 方法区 的一个落地实现。在JDK8被移除。

  • Metaspace(元空间)是 JDK8及之后,废弃了 PermGen space ,取而代之的是 Metaspace , 这是 HotSpot 虚拟机 对 方法区 的新的落地实现。

JDK6、JDK7 时,方法区 就是 PermGen(永久代)。
JDK8 时,方法区就是 Metaspace(元空间)

由于方法区 主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。

package com.aop8.jvm.test;
 
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
 
public class PermGenOomMock{ 
   

	List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
    public static void main(String[] args) { 
   
        URL url = null;       
        try { 
   
            url = new File("/tmp").toURI().toURL();
            URL[] urls = { 
   url};
            while (true){ 
   
                ClassLoader loader = new URLClassLoader(urls);
                classLoaderList.add(loader);
                loader.loadClass("com.aop8.jvm.test.TestDemo");
            }
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
    }
}

运行结果如下:

img

本例中使用的 JDK 版本是7,指定的 PermGen 区的大小为 8M。通过每次生成不同URLClassLoader对象来加载Test类,从而生成不同的类对象,这样就能看到我们熟悉的 java.lang.OutOfMemoryError: PermGen space 异常了。

从此例子中得出:

JDK6 、JDK7 存在 PermGen space

JDK8 中, Hotspot 已经没有 PermGen space ,取而代之是一个叫做 Metaspace(元空间) 。

下面我们就来看看 MetaspacePermGen space 的区别。

2、Metaspace(元空间)

Metaspace(元空间)和 PermGen(永久代)类似,都是对 JVM规范中方法区的一种落地实现

不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存

Oracle 移除PermGen(永久代)从从JDK7 就开始。例如,字符串内部池,已经在JDK7 中从永久代中移除。直到JDK8 的发布将宣告 PermGen(永久代)的终结。

其实,移除 PermGen 的工作从 JDK7 就开始,永久代的部分数据就已经转移到了 Java Heap 或者是 Native Heap。

但永久代仍存在于JDK7 中,并没完全移除,比如:

  • 字面量 (interned strings)转移到 Java heap

  • 类的静态变量(class statics)转移到Java heap

  • 符号引用(Symbols) 转移到 Native heap

必须知道的是 JDK6 、JDK7 依然存在 PermGen space

我们可以通过一段程序来比较 JDK6 、 JDK7 和 JDK8 的区别,以字符串常量为例:

3、JDK6 、JDK7、JDK8 内存溢出的示例

import java.util.ArrayList;
import java.util.List;
 
public class StringOomMock { 
   
    
    static String  base = "string";
    
    public static void main(String[] args) { 
   
        List<String> list = new ArrayList<String>();
        for (int i=0;i< Integer.MAX_VALUE;i++){ 
   
            String str = base + base;
            base = str;
            list.add(str.intern());
        }
    }
}

JDK6 的运行结果:
img

JDK7 的运行结果:
img

JDK8 的运行结果:
img

从运行结果可以得出:

1)、运行时常量池

  • 在 JDK6 ,抛出永久代(PermGen space)异常,说明 运行时常量池 存在于 方法区
  • 在 JDK7、JDK8 抛出堆(Java heap space)异常,说明 运行时常量池 此时在 Java堆 中;

2)、 方法区(永久代元空间):

JDK8 打印ignoring option PermSize=10M; support was removed in 8.0 ... 警告的原因:

  • 我们都知道,JDK8时,永久代已被移除,所以不支持 -XX:PermSize=10M -XX:MaxPermSize=10M 永久代的参数设置。
  • JDK8 的方法区是 元空间,其参数设置是 -XX:MetaspaceSize=N -XX:MaxMetaspaceSize=N
  • 反推证出 JDK6 、 JDK7 时,永久代 还是存在的,否则打印不支持参数设置的警告。

知识扩展:

JDK版本 方法区的实现 运行时常量池所在的位置
JDK6 PermGen space(永久代) PermGen space(永久代)
JDK7 PermGen space(永久代) Heap(堆)
JDK8 Metaspace(元空间) Heap(堆)

4、元空间与本地内存

元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。默认情况下,元空间的大小仅受 本地内存 限制,但可以通过以下参数来指定元空间的大小:

-XX:MetaspaceSize ,初始空间大小:达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间:默认是没有限制的。

除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集;
-XX:MaxMetaspaceFreeRatio ,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集;

现在我们在 JDK8 下重新运行一下上面的代码,不过这次不再指定 PermSize 和 MaxPermSize。而是指定 -XX:MetaspaceSize-XX:MaxMetaspaceSize 的大小。输出结果如下:

img

从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。

5、总结

通过上面分析,大家应该大致了解了 JVM 的内存划分,也清楚了 JDK8 中永久代元空间的转换。不过大家应该都有一个疑问,就是为什么要做这个转换?所以,最后给大家总结以下几点原因:

1)字符串存在永久代中,容易出现性能问题和内存溢出。

2)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

3)永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

4)Oracle 可能会将HotSpot 与 JRockit 合二为一。

转载于:

http://blog.csdn.net/zhyhang/article/details/17246223/

http://www.cnblogs.com/paddix/p/5309550.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/234727.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号