画图学 JVM(十)09 方法区

看图学 JVM 目录 https://blog.csdn.net/weixin_39340061/article/details/106611873

一、内容结构

在这里插入图片描述

二、方法区基本知识

1. 官方介绍

Java 虚拟机规范
在这里插入图片描述

2. 认识方法区

  • 运行时数据区整体结构图
    在这里插入图片描述
  • 从线程共享的角度看
    在这里插入图片描述

3. 核心知识

  1. 与堆一样,也是线程共享的
  2. 在 JVM 启动时创建,结束时销毁
  3. 大小可以选择固定或可扩展
  4. 空间不足时会抛出 OOM

三、方法区演进

1. 方法区在不同 JDK 版本中的演进

在这里插入图片描述

  • JDK 1.6 及以前版本中, 采用永久代实现,静态变量存放在永久代
    在这里插入图片描述
  • JDK 1.7 ,将静态变量和 StringTable 移出永久代,放置到堆空间
    在这里插入图片描述
  • JDK 1.8,方法区被移动到与堆不相连的本地内存,名称也更改为:元空间(Metaspace)。
    在这里插入图片描述JDK 1.8 中,StringTable 和静态变量仍存放在堆区
    在这里插入图片描述

2. 永久代为什么要替换成元空间?

  • 永久代的空间大小难以确定
  1. 在某些场景下,如果动态加载的类过多,容易产生 Perm 区的 OOM。比如一个 Web 工程中,因为功能多,运行时不断动态加载很多类。经常出现 OOM 这样的致命错误。
  2. 元空间与永久代最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间大小仅受本地内存限制。
  • 对永久代进行调优很困难

3. StringTable 为什么要调整?

JDK 7 之前, StringTable 存放在永久代,但永久代在 Full GC 时候才会回收,Full GC 又只在老年代或永久代空间不足是才会触发,从而导致回收效率不高。我们开发中会有大量的字符串被创建, 如果回收效率低,会导致永久代空间不足,抛出 OOM 等致命异常。所以从 JDK 7 开始,StringTable 被移动到堆空间。

四、方法区设置

1. JDK 1.7 及以前设置方法区

  • -XX:PermSize=xxM,设置永久代初始大小,默认值是 20.75 M
  • -XX:MaxPermaSize=xxx, 设置最大空间大小,32 位系统默认值是 64 M,64 位是 82 M。 如果 JVM 加载的信息超过该值,就会抛出 OutOfMemoryError:PermGen Space

2. JDK 1.8 设置方法区

  • -XX:MetaspaceSize,元空间初始大小,也称为高水平线。当方法区内容触及该值时,会触发 Full GC。
  • -XX:MaxMetaspaceSize,元空间最大内存大小,默认值为 -1 (没有限制, 或者说受限于系统可用内存)。如果方法区加载信息超过该值就会抛出:OutOfMemoryError:Metaspace

3. 示例:借助 CGLib 使方法区溢出

	 while (true) {

         Enhancer enhancer = new Enhancer();

         enhancer.setSuperclass(OomObject.class);
         enhancer.setUseCache(false);

         enhancer.setCallback(new MethodInterceptor() {
             @Override
             public Object intercept(Object o, Method method,
                                     Object[] objects,
                                     MethodProxy methodProxy) throws Throwable {
                 return methodProxy.invokeSuper(o, objects);
             }
         });

         enhancer.create();
     }
  • 添加依赖
 	<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.10</version>
    </dependency>

五、方法区内部结构

1. 方法区结构概览

《深入理解 Java 虚拟机》书中对方法区存储内容描述如下:
它用于存储已被虚拟机加载的类型信息、常量、静态变量、及时编译器编译后的代码缓存等。
在这里插入图片描述

2.方法区存储的内容

  • 类型信息
    对每个加载的类型(类、接口、枚举、注解等), JVM 必须在方法区中存储以下类型信息:
    • 完整有效的名称(包名.类名)
    • 直接父类的完整有效名称(对于 interface 或 java.lang.Object,都没有父类)
    • 修饰符(public,abstract, final 等)
    • 直接接口的一个有序列表
  • 域(Filed)信息
    JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序, 域的相关信息包括:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient 的某个子集)
  • 方法(Method)信息
    JVM 必须保存所有方法的以下信息,同域信息一样包括声明顺序(可以按方法定义顺序记忆)
    • 方法修饰符
    • 方法返回类型
    • 方法名称
    • 方法参数的数量和类型(按顺序)
    • 异常表 (异常处理开始位置、结束位置、代码处理在程序计数器中偏移地址、被捕获的异常类的常量池索引等)
    • 方法的字节码、操作数栈、局部变量表及大小

3. 运行时常量池

3.1 字节码文件常量池

一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外。还包含一项信息那就是常量池(constant pool table),包括各种字面量和符号引用。 这部分内容将在类加载后存放到方法区运行时常量池中。官方说明
在这里插入图片描述

3.2 运行时常量池

  • 运行时常量池方法区的一部分。在加载类和接口到虚拟机后,就会创建相应的运行时常量池
  • JVM 为每个类型都维护一个常量池,池中的数据项和数组一样,都是通过索引来访问
  • 运行时常量池中包含许多不同类型的常量, 包括:
  1. 编译期就已经明确的数值字符常量,
  2. 运行期解析后才能获得的方法和字段引用,此时不再是符号引用,已经更换成真实的地址。运行时常量池与 Class 文件常量池的另一个重要特征是具备了动态性
  • 当创建运行时常量表时,如果构造所需内存空间超过了方法区能提供的最大值,则 JVM 会抛出 OOM
运行时常量池中存放的内容
  • 字面量:比较接近 Java 语言层次的常量概念,如文本字符串、被声明为 final 的常量值
  • 符号引用:属于编译原理方面的概念,包括以下三类常量:
  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

六、方法区垃圾回收

1. 方法区的垃圾回收

  1. 《Java 虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区实现垃圾回收。 事实上也确实存在未实现或未能完整实现方法区类型卸载的虚拟机
  2. 在方法区进行垃圾回收效果比较难以让人满意,尤其是类型的卸载条件相当苛刻。但对该区域进行垃圾回收又是必要的。以前 Sun 公司的 Bug 列表中出现过若干严重 Bug 就是由于低版本 HotSpot 虚拟机对此区域不能完全回收导致的内存泄漏

2. 方法区垃圾回收的两个主要部分

2.1 常量池废弃常量

  • HotSpot 虚拟机的常量回收策略:只要常量池中常量没有任何地方引用,就可以被回收
  • 回收废弃常量与回收堆内对象类似

2.2 不再使用的类型信息

判断类型不再使用的条件
  • 实例:该类的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子类的实例
  • 类加载器:加载该类的类加载器已经被回收,这个条件除非是精心设计过的可替换类加载器的场景:如OSGi,JSP 等的重加载等。否则很难达成。
  • Class 对象:该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
不再使用类型的回收
  • Java 虚拟机被允许满足上述三个条件的无用类进行回收,但和对象回收不同,并非没有引用就必然回收。
  • HotSpot 虚拟机提供了 –Xnoclassgc 参数进行控制,还可以用 –verbose:class 以及 –XX:TraceClass-Loading、-XX:TraceClassUnloading 查看类加载和卸载信息
  • 在大量使用反射、动态代理、CGLib等字节码框架,动态生成 JSP 或 OSGi 这类频繁自定义类加载器的场景。通常需要 JVM 具备类型卸载的能力,以保证不会对系统内存形成过大压力。

七、方法区、栈、堆交互

在这里插入图片描述一个简单的新建对象,也会涉及到栈、堆和方法区:

  • 首先会从方法区获取类型信息
  • 在堆中创建对象实例
  • 在栈的当前栈帧中创建引用变量,该引用指向堆中新建的实例

八、总结

一张运行时数据区全景图:
在这里插入图片描述

附件

看图学 JVM 目录

尚硅谷2020最新版宋红康JVM 09 方法区 重绘 PPT

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值