结合 Java 虚拟机规范弄懂方法区

前言

一直以来,对 方法区(Method Area) 的认识比较模糊,所以就抽空结合 JVM规范 、书籍和博客 对其功能和作用进行了研究。

定义

方法区能干什么

结合 Java 虚拟机规范弄懂方法区_第1张图片规范中指出,方法区 的作用在于存储每个类的结构信息,包括运行时常量池、成员变量、成员方法、构造器以及一些特殊方法。成员变量以及成员方法我们都很熟悉了,重点就来看一一下 运行时常量池特殊方法

运行时常量是什么

结合 Java 虚拟机规范弄懂方法区_第2张图片
根据 JVM规范,常量池在Java字节码文件中使用 constant_pool表 来实现,当 constant_pool表 随着字节码被加载到内存后,为 constant_pool表 分配的那块内存以及存储的数据被称为 运行时常量池。至于常量池的作用以及内容,可以查看 《JVM规范中关于常量池的定义》,简单来说常量池存储了类名、类的方法名以及一些字面量等内容,常量池的引入可以节省内存开销,并提高JVM的运行性能。

特殊方法是什么

结合 Java 虚拟机规范弄懂方法区_第3张图片
根据 JVM规范,特殊方法指 方法和方法,但规范中并没有阐释这两个方法的作用,最终在 深入理解Java虚拟机(第2版)中的 第十章javac编译器 中找到了答案:

前面章节中多次提到的实例构造器()方法和类构造器()方法就是在这个阶段添加到语法树之中的(注意,这里的实例构造器并不是指默认构造函数,如果用户代码中没有提供任何构造函数,那编译器将会添加一个没有参数的、访问性(public、protected 或private)与当前类一致的默认构造函数,这个工作在填充符号表阶段就已经完成),这两个构造器的产生过程实际上是一个代码收敛的过程,编译器会把语句块(对于实例构造器而言是“{}”块,对于类构造器而言是“static{}”块)、变量初始化(实例变量和类变量)、调用父类的实例构造器(仅仅是实例构造器,()方法中无须调用父类的()方法,虚拟机会自动保证父类构造器的执行,但在()方法中经常会生成调用iava.lang.Object的()方法的代码)等操作收敛到()和()方法之中,并且保证一定是按先执行父类的实例构造器,然后初始化变量,最后执行语句块的顺序进行

简单来说,Java编译器在编译时,特殊方法 按顺序收录了父类的实例构造器(()方法)、类中定义的代码块以及初始化语句。

实现

Java SE 7 及以前

在《深入理解Java虚拟机(第2版)》的 第2章java内存区域与内存溢出异常 中,对方法区的描述为:

对于习惯在HotSpot虚拟机上开发、部署程序的开发者来说,很多人都更愿意把方法区称为“永久代”(Permanent Generation),本质两者并不等价,仅仅是因为HotSpot 虚拟机
的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。对于其他虚拟机(如如BEAJRockit、IBM-J9等)来说是不存在永交代的概念的。

简单来说,在JDK 7及以前,方法区就是在垃圾回收机制中常提到的持久代区。

Java SE 8

  我们可以大致认为,自 JDK 8 起HotSpot使用 Metaspace 来实现 方法区。在JDK 7 以前的 永久代 中既存储了除了运行时常量池的普通Java对象,又存储了Java类的元信息,而在Metaspace只负责存储类元信息和运行时常量池,原来存储在 永久代 中的普通Java对象被移到了老年代中,这种设计使得Java运行时数据区看上区逻辑更为清晰。
既然提到了Metaspace,我们就不应该止步于这一概念,而应该尝试着分析HotSpot设计团队提出这一概念的理由:Oracle原本就又一个JVM的实现 JRocket,Sun公司被Oracle收购后,Oracle一直在谋求将 JRocket与HotSpot这两款JVM的优秀特性进行融合。而JRocket原本就没有持久代这一概念,而HotSpot中的持久代在实际应用过程中也被证明容易出现OOM——原因在于每次启动JVM,都必须要通过参数(PermSize、MaxPermSize)指定持久代的大小,而持久代的大小是很难调定的,而Metaspace的存储容量只受制于本地内存,所以不易出现OOM。。所以Hotpot设计团队在后来的设计中秉承取求精华,取其糟粕的精神,在后面的JVM实现中也移除了持久代。
  当然以上观点也仅限于笔者多方查询资料进行地合理猜测,如果您有更好地观点,希望您能在评论区提出。

你可能感兴趣的:(JVM)