java虚拟机方法区

方法区的基本概念

  • 方法区和堆一样是多个线程共享的,

  • 《java虚拟机规范》中声明,所有的方法区在逻辑上可以看作是堆的一部分,但是一些简单的实现可能不会选择区进行垃圾收集或者压缩,

  • 而对于hotSpot而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开,所以,方法区可以看作是一块独立与堆的内存空间,而且堆的主要目的是存放创建出来的各种对象,方法区里则会有类的信息,从内容来说,也不相同

  • 方法区在jvm启动时被创建,它的实际物理内存空间和java堆区一样都是可以不连续的

  • 方法区的大小和堆空间一样,可以固定大小也可以扩展

  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法去溢出,虚拟机同样会抛出oom:PermGen space 或者oom : Metspace (1.7之前叫做永久代,1.8叫元空间,报的错误也不一样)

  • 关闭jvm就会释放这个区域的内存

1.7之前习惯性的把方法区叫做永久代,1.8使用元空间取代了永久代,(可以把方法区看作是接口,永久代和元空间都是对应的实现)

  • 方法区和永久代并不等价,只是在hotSpot中是等价的,《java虚拟机规范》中并没有规定具体的实现,不同的虚拟机是不一样的,

  • 永久代使用的是虚拟机的内存,内存方面限制较大,容易溢出

  • 元空间采用的是本地内存来实现的,空间更大

设置方法区的大小

  • jdk1.7的永久代

    -XX:PermSize 初始分配空间 ,默认:20.75M

    -XX:MaxPermSize 最大空间 ,32位物理机默认64M,64位默认82M

  • 1.8 元空间:因为用的是本地内存,所以和平台有关系

    -XX:MetaspaceSize 初始分配空间 ,windows默认21M

    -XX:MaxMetaspaceSize 最大空间 ,默认是-1,也就是没有限制

    • 对于初始的元空间大小,会被认为是高水位线,一旦触及这个水位线,就会执行Full GC,卸载没用的类,然后重置高水位线,新的高水位线的取值取决于GC回收了多少元空间,如果释放的空间不足,在不超过最大空间时,会适当提高水位线,如果释放空间过多,会降低水位线
    • 所以如果初始化水位线设置过低,高水位线会频繁变动,所以建议初始水位线设置一个较高的值

方法区的内部结构

文件被加载到方法区后,会保留是被哪一个类加载器加载的该文件,也算是类的一部分

方法区用于存储已被虚拟机加载的类型信息(类、接口、枚举、注解)、常量、静态变量、即时编译器编译后的代码缓存等

  • 类型信息:对每个加载的类型信息,jvm必须在方法区中存储以下类型信息:

    • 这个类型完整有效名称(全名=包名.类名)
    • 这个类型直接父类的完整有效名(接口或者object是没有父类的)
    • 这个类型的修饰符(public、abstract、final)
    • 这个类型直接接口的一个有序列表(可能会实现了多个接口,是一个有序的列表)
  • 域(Feild,俗称属性)信息

    • jvm必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
    • 域的相关信息包括:域名称、域类型、域修饰符
  • 方法信息:

    • 方法名称
    • 方法返回类型
    • 方法参数的数量、类型(是有序的)
    • 方法修饰符
    • 方法字节码、操作数栈、局部变量表及其大小
    • 异常表(异常开始位置、结束位置等)

non-final的类变量

  • 静态变量和类关联在一起,随着类的加载而加载,成为类数据在逻辑上的一部分
  • 类变量被所有实例共享

全局常量(final 的 static),在编译的时候就会被分配了

运行时常量池

方法区内部包含了运行时常量池。

字节码文件,包含常量池,把常量池的信息加载到内存后,就叫做运行时常量池

常量池包含了各种字面量和对类型、域和方法的符号引用,也就是一些基本信息

常量池可以看作是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息

  • 常量池是字节码文件的一部分,用于存放编译时生成的各种字面量与符号引用,这部分内容在类加载后会存放到方法区的运行时常量池

  • 运行时常量池是方法区的一部分

  • 每个类或接口都会有一个常量池,当中的数据就像数组一样,可以用索引来访问

  • 运行时常量池中,存储的就不是符号引用了,是真实的地址

  • 运行时常量池相较于常量池来说,具备动态性,有可能常量池中并没有相关信息,比如虚方法等

在这里插入图片描述
而字符串常量池再1.6的时候是在永久代的,而1.7就转移到了堆中,1.8虽然去除了永久代,改为元空间,但是字符串常量池依然保留在了堆中,而不是方法区

方法区的演变

只有hotspot才有永久代

在jdk1.6之前,有永久代,而且静态变量存放在永久代上(new的对象的实体始终在堆中,引用在永久代)

1.7,依然有永久代,但是已经在逐步去除永久代了,字符串常量池、静态变量等从永久代中移除,转移到堆中

1.8及以后,就没有永久代了,类型信息、字段、方法、常量保存至本地内存的元空间,但是字符串常量池、静态变量依然放在堆中

为什么要用元空间代替永久代

官方给的消息是,jdk8 ,oracle将hotSport 和 JRockit 进行了整合,而 JRockit 是没有永久代的,所以1.8也没有永久代

也有以下原因:

  • 永久代的空间大小难以确定,如果动态加载的类太多,会频繁出现错误,而元空间采用的是本地内存,不受虚拟机内存的限制
  • 对永久代的调优很难,想要回收类,条件很苛刻(full GC 代价很大)

字符串常量池的变化

jdk 7 将StringTable放到了堆空间,因为永久代的回收效率很低,只有在full gc 是才会触发,而full GC 是老年代、永久代不足时才会触发,这就使得StingTable回收效率不高,而一般开发中通常会有大量的字符串被创建,如果回收效率低,容易导致永久代内存不足,放在堆里。可以及时的回收内存

方法区的垃圾回收

《java虚拟机规范》对方法区的约束是比较宽松的,方法区的垃圾回收,在不同的虚拟机实现是不同的,是可以不进行垃圾回收的

一般来说,方法区的垃圾回收效果是比较困难的,尤其是类型的卸载,条件十分苛刻,但是方法区的回收有时又是非常由必要的,否则有可能会出现内存泄漏

方法区的垃圾回收主要涉及两部分:

  • 常量池中废弃的常量:字面量和符号引用
    • 字面量:文本字符串、final的常量值 (比较接近java语言层次)
    • 符号引用:类、接口的全限定名,字段的名称和描述符,方法的名称和描述符 (属于编译原理的概念)
    • 对于常量池的回收策略很明确:只要常量池中的常量没有被任何地方引用,就可以回收
  • 不在使用的类型:想要判定类型是否是不在被使用的条件是很苛刻的,需要满足以下全部条件
    • 所有的实例都被回收了,且不存在任何子类实例
    • 加载该类的类加载器已经被回收了
    • 该类的 .class 对象没有被任何地方引用
    • 只有全部满足上面的三个条件,才允许被回收,而不是一定会回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值