JDK 1.8 下的 java.lang.Class 对象和 static 成员变量在堆还是方法区?

背景

从 JDK 1.7 开始,Oracle 团队就开始对 HotSpot VM 的永久代(PermGen)大刀阔斧的修改、移除,导致 HotSpot 的内存区域发生了很多改变,最终在 JDK 1.8 元空间(Metaspace)取代了永久代成为 HotSpot VM 对方法区的实现。

我们入门虚拟机的学习大多是通过《Java 虚拟机规范》、《深入理解Java虚拟机》这两本经典。但是由于 Java 环境复杂、JDK版本更新、市面上的虚拟机型号众多等问题,这两本书只能帮助我们解决一些宏观认识的问题,对于一些细节问题还是需要我们结合具体的环境来看。

本文主要研究的问题是 java.lang.Class 对象和 static 成员变量在运行时内存的位置。这里先给出结论,JDK 1.8 中,两者都位于堆(Heap),且static 成员变量位于 Class对象内。

相信读者都曾阅读过《深入理解Java虚拟机 第2版》中的下面这两段话:

    方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。
    …
    加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中,方法区中的数据存储格式由虚拟机实现自行定义,虚拟机规范未规定此区域的具体数据结构。然后在内存中实例化一个java.lang.Class的对象(并没有明确规定是在java堆中,对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面)
    …

在笔者写这篇文章的时候,暂时还没有找到第3版,而第2版是基于 JDK 1.7 的,接下来我们整理一下网上的博客与实验,看看 JDK 1.8 中这些变化的产生。
java.lang.Class

我们知道在类加载(Class Loading)的 5 个过程中,加载(Loading)的最终产物是一个 java.lang.Class 对象,它是我们访问方法区中的类型数据的外部接口,我们需要通过这个对象来访问类的字段、方法、运行时常量池等。

在这里插入图片描述

那么这个 Class 对象在哪里呢?下面是搜集的几份资料。

    hotspot java虚拟机Class对象是放在方法区还是堆中 ? - 潜龙勿用的回答 - 知乎

        Class对象是存放在堆区的,不是方法区,这点很多人容易犯错。类的元数据(元数据并不是类的Class对象!Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的)才是存在方法区的。
        …

    这个回答指出 Class 对象并非在方法区中,但没有指出JDK的版本,不过里面详细收录了 JVM 需要保存的 .class 文件数据结构(元数据)。特别推荐看这个回答最后举例代码怎样调用方法区的信息,很有用!

    hotspot java虚拟机Class对象是放在方法区还是堆中 ? - ETIN的回答 - 知乎

    这个回答直接从 openJDK 1.8 中关于虚拟机实现的源码入手,分析了 Class 对象分配内存的过程,最后指出 Class 确实是分配在 Heap 上。openJDK 1.8 这部分源码是用 C/C++ 写的,初学者慎入!(有理有据,代码说话,但我看不懂…)

    Java static变量保存在哪?

    这篇博客我也很推荐大家看,在 1.8 环境下,作者通过一些调试工具打印出虚拟机内存,追踪 Class 对象的分配,明确了 Class 对象的真实地址就是在堆中,并且, 在 Class 的实例中找到静态成员变量的分配位置。(一石二鸟,好文,调试的工具可以学一学)

static 成员变量

上一节其实已经通过实验知道 static 成员变量在 Class 对象里,也就是在 Heap 上,这里进一步分析这个问题。

java中的静态变量和Class对象究竟存放在哪个区域? - ETIN的回答 - 知乎 还是通过源码分析静态成员的分配,初学者慎入!

而其实在官方的 Bug 文档中已经提到了 static 成员变量位置变化的说明,JDK-7017732 : move static fields into Class to prepare for perm gen removal 里提到为了迎合移除永久代的需要,静态字段被移到了 Class 对象中。这里摘一段关键:

    Currently static fields are stored in the instanceKlass but when those are moved into native memory we’d have to have a new card mark strategy for static fields. This could be something like setting a flag in the instanceKlass and then rescanning every klass during a GC which seems expensive or marking the card for the java.lang.Class then making sure to scan the instanceKlass when scanning the Class. If we move them into the Class then almost all the existing machinery works exactly as it always has. The only execution difference is which constant is materialized for the field access.

看不懂没关系,看标题意思意思也差不多了。
题外话

由于 JDK 版本的变化,一些经典的书会在某些方面给我们带来很多误解,这是不可避免的,我们要做的就是根据实际解决问题,如果我们还不具备自己验证的能力,也不要吝惜请教。最后,送大家一句话:“尽信书不如无书。”
————————————————
版权声明:本文为CSDN博主「XuJiaLe1997」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xu_jl1997/article/details/89433916

你可能感兴趣的:(java,jvm)