写点什么

剖析一下 JVM 中的方法区

用户头像
科比信徒
关注
发布于: 2021 年 01 月 25 日
剖析一下JVM中的方法区

首先我们来看下 JVM 运行时数据区的图

方法区 就在这个图中

那么 JVM 中 为什么要有方法区呢?他的作用又是什么呢?既然是数据区那么里面都是存放了些什么东西?


我们先来看一下一个对象的访问定位的过程

然后我们从图片可以发现一个对象的访问过程 涉及到 方法区,栈,堆的交互

也可以看出

  • Person:存放在元空间中

  • person:存放在栈的局部变量表中

  • new Person():存放在 jvm 堆中

所以我们就知道方法区是会存放类信息的


我们来看一下 JDK8 中图


可以看到 meta space 他是独立于堆外的一块东西,他使用的内存是本地内存

根据《Java 虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,也会抛出 OOM 异常


那么竟然是本地内存,那我们如何设置这个内存的大小?

方法区的大小不必是固定的,JVM 是可以根据应用的需要动态调整这个大小的。

如果要设置的话 JDK8

-XX:MetaspaceSize 设置元空间的初始大小

-XX:MaxMetaspaceSize 设置元空间的最大大小

默认值:windows 是 MetaspaceSize = 21M MaxMetaspaceSize = -1 即没有限制

所以默认情况下,虚拟机会好久所有的可用系统内存,那么元数据区发生溢出,虚拟机一样会抛出 OOM 异常 OutOfMemoryError:Metaspace


-XX:MetaspaceSize:设置初始的元空间大小,对于 64 位的服务器来说,其默认值就是 21MB。其实这个也叫初始的高水位线,一旦触及这个水位线,就会发生 Full GC 然后卸载一些没用的类 然后这个高水位线将会重置。新的高水位线的值取决于 GC 后释放了多少元空间。如果释放之后空间还是不足,那么在不超过 MaxMetaspaceSize 条件下,适当提高最高水位线,如果释放空间过多,就降低该值。


我们来看看方法区的内部结构

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


我们先记住一个大概,我们来实际举一个例子看看到时候在反过来看这里内部结构的东西

如下代码

public class MethodAreaDemo {    public static void main(String args[]) {        int x = 500;        int y = 100;        int a = x / y;        int b = 50;        System.out.println(a+b);    }}
复制代码

接着我们使用 javap 工具给反编译一下,看下他的 class 文件

 public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=3, locals=5, args_size=1         0: sipush        500         3: istore_1         4: bipush        100         6: istore_2         7: iload_1         8: iload_2         9: idiv        10: istore_3        11: bipush        50        13: istore        4        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;        18: iload_3        19: iload         4        21: iadd        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V        25: return
复制代码

我们关注一下 第 15 行 getstatic #2 第 22 行 invokevirtual #3 这个 #2 和 #3 玩意是个什么东西

在反编译出来的 class 文件中

上面有一个这种信息 Constant Pool

其实这个就是下面代码中的引用,其实在代码中我们那种引用 就叫符号引用


一个 java 源文件中的类、接口,编译后产生一个字节码文件。而 Java 中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池中。


不太好理解,我举个例子,比如你 写了今天的日程安排


1、吃早餐

2、开车去逛街

3、吃晚饭

这个就相当于你定义好了一个 java 文件的运行逻辑,然后开车去逛街,你肯定是在你要去开车的时候才知道你要开的具体的车,这个车可能是你一家共用的对吧?


所以总得来讲就是 每个类都有一个常量池表,它是 Class 文件的一部分,用于存放编译器生成的各种字面量与符号引用,这部分内容会在类加载后存放到方法区的运行时常量池中。


运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。

常量池表(Constant Pool Table)是 Class 文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池。

JVM 为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。

运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。

运行时常量池,相对于 Class 文件常量池的另一重要特征是:具备动态性。

运行时常量池类似于传统编程语言中的符号表(symboltable),但是它所包含的数据却比符号表要更加丰富一些。

当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则 JVM 会抛 outofMemoryError 异常。


所以我们目前就知道了方法区有两个东西 类信息、运行时常量池


至于为什么需要方法区

我们可以看到方法区中存放的信息 都是一些大部分固定不动的东西,类信息一般加载进去了 都是要用的东西固定不动,常量池就更是了

这肯定是和 GC 有关对吧,就是把加载进来的东西不容易成为垃圾的内存放在一块区域,就方便了 GC


那么方法区是否会垃圾回收呢?

肯定是会的

方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。

但是这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。


总结


用户头像

科比信徒

关注

还未添加个人签名 2020.02.13 加入

还未添加个人简介

评论

发布
暂无评论
剖析一下JVM中的方法区