Memory Model -- 06 -- Java内存模型(五、方法区)

在这里插入图片描述


一、方法区 (Method Area)

  • 方法区 (Method Area) 与 Java 堆一样,是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

  • 当方法区无法满足内存分配的需求时,将会抛出 OutOfMemoryError 异常


二、永久代与元空间

  • 在 Java 虚拟机规范中,只规定了方法区的概念及其作用,但并没有规定如何去实现它,这里我们来看看方法区的两个具体实现:永久代与元空间

  • 两者最大的区别是:元空间使用的是本地内存,而永久代使用的是虚拟机的内存

    • 永久代 (PermGen)

      • 在 Java8 之前,永久代是方法区的具体实现 (仅针对 HotSpot 虚拟机,对于其他虚拟机如:BEA JRockit、IBM J9 等来说是不存在永久代的)

      • 使用永久代来实现方法区,容易遇到内存溢出的问题,我们可以通过 ‑XX:MaxPermSize 参数来设置永久代的大小,一旦类的元数据超过了设定的大小,就会抛出 java.lang.OutOfMemoryError: PermGen space 异常,而且有极少数的方法 (例如:String.intern() 方法) 会因为这个原因导致在不同的虚拟机下有不同的表现

      • 从 Java7 开始,原先位于方法区中的字符串常量池已被移动到 Java 堆中

    • 元空间 (Metaspace)

      • 从 Java8 开始,Java 虚拟机移除了永久代,转而使用元空间来代替它,与永久代不同的是,元空间使用本地内存来存放类的元数据而不是使用虚拟机的内存

        为什么废除永久代 --> 官方说明

      • 使用本地内存后,最直接的表现在于,原先在永久代中会出现的异常 - java.lang.OutOfMemoryError: PermGen space 将不复存在,因为默认类的元数据分配只受本地内存大小的限制,理论上本地内存有多少,元空间就可以有多大,这解决了内存空间不足的问题

      • 虚拟机会在运行时会根据类的元数据大小动态地设置元空间的大小


三、元空间的内存管理

在这里插入图片描述

  • 元空间的内存管理由元空间虚拟机来完成,先前使用永久代时,对于类的元数据我们需要用不同的垃圾回收器来进行处理,现在只需要执行元空间虚拟机的 C++ 代码即可完成

  • 在元空间中,类和其元数据的生命周期和对应的类加载器是相同的,话句话说,只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉

  • 每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活时,其对应的元空间就会被回收,在回收过程中没有重定位和压缩等操作,但是元空间内的元数据会进行扫描来确定 Java 引用

  • 元空间虚拟机负责元空间的分配,其采用的形式为组块分配,组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表,当一个类加载器需要组块时,它就会从这个全局的组块列表中获取并维持一个自己的组块列表。当一个类加载器不再存活时,那么其持有的组块将会被释放,并返回给全局组块列表。类加载器持有的组块又会被分为多个块,每一个块存储一个单元的元信息,组块中的块是线性分配的 (指针碰撞分配形式)。组块分配至内存映射区域,这些全局的虚拟内存映射区域以链表的形式连接,一旦某个虚拟内存映射区域被清空,那么这部分的内存就会返回给操作系统


四、元空间相比永久代的优势

  • 字符串常量池存在永久代中,容易出现性能问题和内存溢出

  • 类和方法的信息大小难以确定,给永久代的大小指定带来困难 (通过 ‑XX:MaxPermSize 参数来指定),太小容易导致永久代内存溢出,太大容易导致老年代内存溢出

  • 永久代会为 GC 带来不必要的复杂性,且回收效率偏低,因为对永久代进行调优是很困难的,永久代中的元数据可能会随着每一次 Full GC 发生而进行移动,HostSpot 虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据,将元数据分离出来后,可以简化 Full GC 以及对以后的并发隔离类类元数据等方面进行优化

  • 方便 HotSpot 与其他虚拟机 (如:JRockit) 进行集成


五、归纳总结

  • 方法区

    • 用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

    • Java8 之前,方法区的具体实现为永久代;Java8 开始,方法区的具体实现为元空间

    • 元空间使用的是本地内存,而永久代使用的是虚拟机的内存

  • 永久代

    • 使用虚拟机内存

    • 难以指定大小,容易出现性能问题和内存溢出

      • 太小容易导致永久代内存溢出

      • 太大容易导致老年代内存溢出

    • 从 Java7 开始,将原先位于方法区中的字符串常量池移动到了 Java 堆中

  • 元空间

    • 使用本地内存

    • 虚拟机在运行时会根据类的元数据大小动态地设置元空间的大小


六、参考资料

发布了106 篇原创文章 · 获赞 83 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Goodbye_Youth/article/details/103247631