Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >jvm之方法区演变及方法区的GC解读

jvm之方法区演变及方法区的GC解读

作者头像
一个风轻云淡
发布于 2023-10-15 01:49:59
发布于 2023-10-15 01:49:59
24200
代码可运行
举报
文章被收录于专栏:java学习javajava学习java
运行总次数:0
代码可运行

方法区的演进细节

首先明确:只有Hotspot才有永久代。BEA JRockit、IBMJ9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一

Hotspot中方法区的变化:

JDK1.6及之前

有永久代(permanet),静态变量存储在永久代上

JDK1.7

有永久代,但已经逐步 “去永久代”,字符串常量池,静态变量移除,保存在堆中

JDK1.8

无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中。

 为什么永久代要被元空间替代?

官网地址:JEP 122: Remove the Permanent Generation (java.net)

JRockit是和HotSpot融合后的结果,因为JRockit没有永久代,所以他们不需要配置永久代

随着Java8的到来,HotSpot VM中再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域叫做元空间(Metaspace)。

由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。

这项改动是很有必要的,原因有:

  • 为永久代设置空间大小是很难确定的。在某些场景下,如果动态加载类过多,容易产生Perm区的oom。比如某个实际Web工 程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。

"Exception in thread 'dubbo client x.x connector' java.lang.OutOfMemoryError:PermGen space"

而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 因此,默认情况下,元空间的大小仅受本地内存限制。

  • 对永久代进行调优是很困难的。

有些人认为方法区(如HotSpot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如JDK 11时期的ZGC收集器就不支持类卸载)。 一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前Sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏

方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型

StringTable为什么要调整位置?

jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。

这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

静态变量存放在那里?
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 静态引用对应的对象实体始终都存在堆空间
 * jdk7:
 * -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
 * jdk8:
 * -Xms200m -Xmx200m-XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
 */
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];
    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);
        
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * staticobj、instanceobj、Localobj存放在哪里?
 */
public class StaticobjTest {
    static class Test {
        static ObjectHolder staticobj = new ObjectHolder();
        ObjectHolder instanceobj = new ObjectHolder();
        void foo(){
            ObjectHolder localobj = new ObjectHolder();
            System.out.println("done");
        }    
    }
    private static class ObjectHolder{
        public static void main(String[] args) {
            Test test = new StaticobjTest.Test();
            test.foo();
        }
    }
}

使用JHSDB工具进行分析,这里细节略掉

 staticobj随着Test的类型信息存放在方法区,instanceobj随着Test的对象实例存放在Java堆,localobject则是存放在foo()方法栈帧的局部变量表中。

测试发现:三个对象的数据在内存中的地址都落在Eden区范围内,所以结论:只要是对象实例必然会在Java堆中分配。

接着,找到了一个引用该staticobj对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面有一个名为staticobj的实例字段:

从《Java虚拟机规范》所定义的概念模型来看,所有Class相关的信息都应该存放在方法区之中,但方法区该如何实现,《Java虚拟机规范》并未做出规定,这就成了一件允许不同虚拟机自己灵活把握的事情。JDK7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射class对象存放在一起,存储于Java堆之中,从我们的实验中也明确验证了这一点

方法区的垃圾回收

有些人认为方法区(如Hotspot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如JDK11时期的zGC收集器就不支持类卸载)。

一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。

先来说说方法区内常量池之中主要存放的两大类常量:字面量和符号引用。字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

HotSpot虚拟机对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收。

回收废弃常量与回收Java堆中的对象非常类似。

判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。关于是否要对类型进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class 以及 -XX:+TraceClassLoading-XX:+TraceClassUnLoading查看类加载和卸载信息

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JVM之方法区
ThreadLocal:如何保证多个线程在并发环境下的安全性?典型应用就是数据库连接管理,以及独立会话管理
冬天vs不冷
2025/01/20
930
JVM之方法区
JVM内存与垃圾回收篇第9章方法区
ThreadLocal:如何保证多个线程在并发环境下的安全性?典型应用就是数据库连接管理,以及独立会话管理
yuanshuai
2022/08/17
4120
JVM内存与垃圾回收篇第9章方法区
面试官:说下你对方法区演变过程和内部结构的理解
之前我们已经了解过“运行时数据区”的程序计数器、虚拟机栈、本地方法栈和堆空间,今天我们就来了解一下最后一个模块——方法区。
阿Q说代码
2021/09/29
4860
jvm之方法区解读
官方文档:Chapter 2. The Structure of the Java Virtual Machine
一个风轻云淡
2023/10/15
2540
jvm之方法区解读
Java基础知识:JVM内存结构
jvm将虚拟机分为 5大区域 ,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;
DioxideCN
2022/08/05
7570
Java基础知识:JVM内存结构
JVM-方法区
方法区概述 《Java虚拟机规范》中明确说明:"尽管所有额方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会去进行垃圾收集或者进行压缩",但是对于HotspotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆区分开,所以,方法区看做是一个独立于Java堆的内存空间
是小张啊喂
2023/02/21
3550
JVM运行时数据区-方法区
JVM运行时数据区-方法区 方法区和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、域信息、方法信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。很多人都更愿意把方法区称为“永久代”(Permanent Generation)。从jdk1.7已经开始准备“去永久代”的规划,jdk1.7的HotSpot中,已经把原本放在方法区中的静态变量、字符串
晓果冻
2022/09/08
3760
JVM运行时数据区-方法区
阿里云二面:JVM 方法区和元空间什么关系?为什么要将永久代替换为元空间?
昨天,我花了很长时间完善了一下 JavaGuide 上 JVM 部分方法区的相关介绍。
Guide哥
2022/04/11
9200
阿里云二面:JVM 方法区和元空间什么关系?为什么要将永久代替换为元空间?
JVM:内存结构
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机概念模型里(概念模型,各种虚拟机可能会通过一些更高效的方式实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令(分支、跳转、循环、异常处理、线程恢复等基础操作都会依赖这个计数器来完成)。
HLee
2021/02/18
7730
JVM:内存结构
每日知识集之JVM篇
Java的指令都是根据栈来设计的,栈是运行时的单位,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用。
余生大大
2022/11/02
4240
每日知识集之JVM篇
JVM-方法区
ThreadLocal:如何保证多个线程在并发环境下的安全性?典型应用就是数据库连接管理,以及独立会话管理
utopia
2023/03/21
3260
JVM-方法区
jvm入门4:09方法区
1方法区域对一样,是各线程共享的内存区域;2在jvm启动时被创建,实际物理内存空间中和java堆区一样都是不连续的;3大小可选择固定或扩展;4方法区的大小决定了可以保存多少类,方法区溢出,虚拟机会报内存溢出错误,outofmemoryerror:pergen space、metaspace,如加载大量的第三方jar包,tomcat部署的工程过多30-50个,大量动态的生成反射类;关闭jvm就会释放这个区域的内存
用户10832809
2025/02/25
1280
jvm回收方法区
很多人认为方法区(或者HotSpot虚拟机中的永久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集的“性价比”一般比较低
earthchen
2020/09/24
6190
【JAVA基础☞内部存储和GC】Java方法区和永久代
这里只讨论HotSpot虚拟机,这也是目前使用的最多的JVM。Sun JDK7 HotSpot虚拟机的内存模型如下图所示:
用户5640963
2019/07/25
1.2K0
深入理解JVM虚拟机---JVM内存管理
​ 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里 ,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
俺也想起舞
2020/05/06
4480
运行时常量池与字符串常量池_常量池是什么
我们知道在JDK1.8中取消了永久代,区而代之使用了元空间来实现方法区。话虽如此,但是关于字符串常量池和运行时常量池的模棱两可的说法一直都是争论不休的。
全栈程序员站长
2022/09/19
5420
运行时常量池与字符串常量池_常量池是什么
JVM 内存结构解析
以前的方法区(或永久代),用来存放class,Method等元数据信息,但在JDK1.8已经没有了,取而代之的是MetaSpace(元空间),元空间不在虚拟机里面,而是直接使用本地内存。
CoderJed
2018/09/13
1.6K1
JVM 内存结构解析
JVM内存模型1 程序计数器2. Java虚拟机栈(JVM Stack)3. 本地方法栈(Native Method Stack)4 Java堆(Java Heap)5 方法区6 直接内存(Direc
JVM内存模型 1 程序计数器 1.1. 定义 程序计数器是一块较小的内存空间,可看作当前线程正在执行的字节码的行号指示器 如果当前线程正在执行的是 Java方法 计数器记录的就是当前线程正在执行的字节码指令的地址 本地方法 那么程序计数器值为undefined 1.2. 作用 程序计数器有两个作用 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程
JavaEdge
2018/05/16
1.4K0
jvm原理——第一篇jvm的运行模式
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行,属于用户态。
胡齐
2019/12/19
1.4K0
jvm原理——第一篇jvm的运行模式
JVM - 运行时数据区
Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。
用户2987604
2020/06/15
3560
JVM - 运行时数据区
相关推荐
JVM之方法区
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验