JVM 方法区
1. 前言
本节主要讲解运行时数据区的方法区。本节主要知识点如下:
- 了解方法区的作用及意义,为本节的基础知识;
- 了解方法区存放数据类型,为本节重点内容之一;
- 了解运行时常量池,我们在学习Class文件结构的时候,也学习过常量池结构,那么运行时常量池本节课程会进行讲解;
- 了解方法区与堆内存结构的关系,以JDK 1.8 版本为分界线,进行对比讲解,为本节重点内容之一。
2. 什么是方法区
定义:方法区,也称非堆(Non-Heap),是一个被线程共享的内存区域。其中主要存储加载的类字节码、class/method/field 等元数据对象、static-final 常量、static 变量、JIT 编译器编译后的代码等数据。另外,方法区包含了一个特殊的区域 “运行时常量池”。
Tips:对于运行时常量池,后文会有讲解。
对于习惯在 HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为 “永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为 HotSpot 虚拟机的设计团队选择把 GC 分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如 BEA JRockit、IBM J9 等)来说是不存在永久代的概念的。
3. 方法区存放的数据
在讲解方法区内存放的数据之前,我们先通过示意图来直观的看下,方法区存放的数据与堆内存之间的关系。如下图所示:
从图中可以看到,方法区存放了 ClassLoader 对象的引用,也存放了一个到类对象的引用,这两个引用的对象实例会存放到堆内存中。从上图我们就可以简单的了解到方法区存放的数据是什么,接下来,我们对存放的数据类型进行解释。
- 类型全限定名:全限定名为 package 路径与类名称组合起来的路径;
- 类型的直接超类的全限定名:父类或超类的全限定名;
- 类型是类类型还是接口类型:判定当前类是 Class 还是接口 Interface;
- 类型的访问修饰符:判断修饰符,如 pulic,private 等;
- 类型的常量池:这部分会在下文进行讲解;
- 字段信息:类中字段的信息;
- 方法信息:类中方法的信息;
- 静态变量:类中的静态变量信息;
- 一个到类 ClassLoader 的引用:对 ClassLoader 的引用,这个引用指向对内存;
- 一个到 Class 类的引用:对对象实例的引用,这个引用指向对内存。
4. 运行时常量池
我们先来回顾下Class 文件结构中的常量池的相关知识。
Class 文件中的常量池:
在 Class 文件结构中,最头的 4 个字节用于存储 Megic Number,用于确定一个文件是否能被 JVM 接受,再接着 4 个字节用于存储版本号,前 2 个字节存储次版本号,后 2 个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个 u2 类型的数据 (constant_pool_count) 存储常量池容量计数值。
常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。更加具体的知识,同学们可以翻看之前相关的小节内容。
运行时常量池:我们回到正题,来看下运行时常量池。
Tips:其实 Class 文件中的常量池与运行时常量池的关系非常容易理解,Class 文件中的常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。简单总结来说,编译器使用 Class 文件中的常量池,运行期使用运行时常量池。
运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量一定只有编译期才能产生,也就是并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是 String 类的 intern() 方法。
5. 常量池的优势
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
- 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
- 节省运行时间:比较字符串时,
==
比equals ()
快。对于两个引用变量,只用==
判断引用是否相等,也就可以判断实际值是否相等。
6. 方法区内存变更
方法区的实现,虚拟机规范中并未明确规定,目前有 2 种比较主流的实现方式:
HotSpot 虚拟机 1.8之前:在 JDK1.6 及之前版本,HotSpot 使用 “永久代(permanent generation)” 的概念作为实现,即将 GC 分代收集扩展至方法区。这种实现比较偷懒,可以不必为方法区编写专门的内存管理,但带来的后果是容易碰到内存溢出的问题(因为永久代有 - XX:MaxPermSize 的上限)。
在 JDK1.7,HotSpot 逐渐改变方法区的实现方式,如 1.7 版本移除了方法区中的字符串常量池,但为发生本质的变化。
HotSpot 虚拟机 1.8之后:1.8 版本中移除了方法区并使用 metaspace(元数据空间)作为替代实现。metaspace 占用系统内存,也就是说,只要不碰触到系统内存上限,方法区会有足够的内存空间。但这不意味着我们不对方法区进行限制,如果方法区无限膨胀,最终会导致系统崩溃。
7. 小结
本节主要讲解了运行时数据区里边的方法区,方法区是一块共享内存区域,在运行时数据区占据着十分重要的位置。我们了解了方法区里边存储的数据类型,也了解到了方法区的作用,同时了解了方法区内存的版本变更,通篇皆为重点知识,学习者需要用心学习。