憨人笔记之JVM-运行时数据区(方法区)

371 阅读4分钟

话不多说,干就完了。

方法区

方法区同Java堆一样,也是线程共享区域。方法区主要存储已经被虚拟机加载的类信息(包括类的名称、方法信息、字段信息)、常量、静态变量、即时编译器编译后的代码(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)等数据。

很多人又把方法区称为"永久代",其实这个说法并不等价。仅仅是因为HotSpot虚拟机选择把GC分代收集拓展到方法区,或者说是使用永久代来实现方法区而已。对于其他的虚拟机来说是不存在永久代的概念的。

方法区除了和堆一样可以不需要连续的内存,也可以选择固定的大小或者动态拓展,还能够选择是否实现垃圾收集。垃圾收集相对来说在方法区会比较少的出现。较少的出现并不意味着不会出现垃圾收集内存回收的情况,方法区的垃圾收集主要是针对常量池的回收和类型的写在。

若方法区无法满足内存分配需求时,会抛出OOM异常。

运行时常量池

运行时常量池是属于方法区的一部分,通常存储的是在编译期所产生的字面量及符号引用,如下图所示:

在类加载之后,会将这部分内容由class常量池保存到运行时常量池中。

在说到类加载子系统时,类的加载由加载、链接(验证、准备、解析)、初始化、使用等这么几个阶段,而在类加载阶段,主要做了以下几件事情:

  1. 通过类的全限定名获取二进制字节码文件(class文件)
  2. 将类文件中的静态存储结构加载到运行时数据区中
  3. 在内存中生成java.class.Class对象,作为方法区这个类的各种数据访问的入口

但是需要特别说明的是,类加载阶段所产生的类对象实例对象本质上是有区别的。类对象仅仅是在类加载的时候生成到内存中的对象,而实例对象一般是通过new关键字所创建的对象。它们的内存存储区域也不一样。在上述步骤2中,所谓静态存储结构加载到运行时数据区中其实际上就是将class常量池中的内容保存到运行时常量池中。

常量池中还保存这符号引用,在类的解析阶段,会将这些符号引用转换成直接引用(直接指向对象实例的指针地址)保存到常量池中。所以,在整个类加载阶段,有两个环节会将数据保存至常量池中。

  • 加载:将class常量池中的数据保存到运行时常量池中。
  • 解析:将一部分符号引用转换成直接引用保存到运行时常量池中。(ext:通常是在编译期就确定了调用版本的方法,例如类实例方法、私有方法、静态方法和父类方法等)

运行时常量池相对于类常量池另一个重要的特征就是具备动态性,Java并不要求常量一定要在编译器就产生,也就是说并不是一定要在class常量池中的内容才能够进入到运行时常量池,在程序运行期间,也可能将常量放入到常量池中。例如String类的intern()方法。

常量池的优点

常量池属于方法区的一部分那么也是线程共享的,既然内存区域是共享的,那么某种程度上就减轻了新对象频繁创建销毁的内存开销,实现对象的共享。

例如字符串常量池:把所有字符串常量放到一个常量池中

  1. 节省内存空间:常量池中所有一样的字符串常量会被合并至占用一个内存空间
  2. 节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

运行时常量池属于方法区的一部分,所以同方法区一样,当出现内存不足时,也会出现OOM异常。


不怕路歹行不怕大雨淋,心上一字敢 面对我的梦,甘愿来作憨人。 --<憨人>