起笔
文章稍迟但到,上个星期送家里小侄子去医院输液导致文章没来得及更新,不过系列文章是不会断更的,只是更新时间的快慢问题。
做自己生命的主角,而不是别人生命中的看客
参考书籍:“深入理解java虚拟机”
方法区
方法区(Method Area):与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译(编译器是执行引擎中会介绍的内容)后的代码缓存等数据。
虽然在“java虚拟机规范”中把方法区描述为堆的一个逻辑部门,但是它却又一个别名叫“非堆(Non-Heap)”,目的是与java堆区分开来。
说到方法区,不得不提一下永久代(Permanent Generation),很多人估计会把这两者混淆,认为永久代就是方法区,其实这两者本质并不是等价的,至于为什么有人会认为它两一样的原因是当时的hotspot虚拟机设置团队选择把收集器的分代设计扩展到方法区,或者说使用永久代来实现方法区而已。但是对于BEA、JRockit、IBM J9等虚拟机来说,是不存在永久代的概念的,并且hotspot的这种设计回头来看更容易导致java应用遇到内存泄漏的问题(永久代有–XX:MaxPermSize的上限,即使不设置也有默认大小,其他虚拟机则没有这种内存大小的设置,在防止内存泄漏方面表现更好如JRockit)。
从上面介绍可以知道,hotspot虚拟机中的方法区的实现是通过堆中分代设计中的永久代实现的,而永久代这种设计在防止内存泄漏表现得不尽人意,其实Oracle公司也想过要放弃永久代,在JDK6的时候,HotSpot的开发团队就开始放弃永久代的概念,逐步改为采用本地内存(Native Memory 也就是常说的元空间)来实现方法区。到了JDK7的HotSpot,已经把原来放在永久代的字符串常量池、静态变量等移出。在JDK8的时候终于完全废弃了永久代的概念,改用了在本地内存中实现的元空间。
再稍微介绍一下类型信息、方法信息、常量池、静态变量、即时编译器编译(编译器是执行引擎中会介绍的内容)后的代码缓存这几个概念:
类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法中存储一下类型信息:
- 这个类型的完整有效名称(全名 = 包名.类名)
- 这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类)
- 这个类型的修饰符(public,abstract,final的某个子集)
- 这个类型直接接口的一个有序列表
方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
- 方法名称
- 方法的返回类型
- 方法参数的数量和类型(按顺序)
- 方法的修饰符(public,private,protected,static,finnal,synchronized,native,abstract的一个子集)
- 方法的字节码(bytecodes),操作数栈,局部变量表及大小(abstract和native方法除外)
- 异常表
域(Field)信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序:
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
运行时常量池
- 运行时常量池(Runtime Constant Pool)是方法区的一部分
- 常量池表(Constant Pool Table)是Class文件的一部分,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
- 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
- JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。
- 运行时常量池中包含多种不同的常量,包括编译器就已经明确的数值字面量,也包括到运行期解析后才能够获取的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。
- 运行时常量池,相对于Class文件常量池的另一重要特征是:具备动态性
- 运行时常量池类似于传统编程语言中的符号表(symbol table),但是它包含的数据却比符号表要更加丰富一些。
- 当创建或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛出OutOfMemoryError异常。