资讯专栏INFORMATION COLUMN

方法区到底是个什么鬼

binaryTree / 852人阅读

摘要:那方法区里都存着什么呢先抛结论静态变量常量类信息构造方法接口定义运行时常量池存在方法区中。动态常量池运行时常量池是方法区的一部分,是一块内存区域。文件常量池将在类加载后进入方法区的运行时常量池中存放。

一、方法区与永久代

这两个是非常容易混淆的概念,永久代的对象放在方法区中,就会想当然地认为,方法区就等同于持久代的内存区域。事实上两者是这样的关系:

《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 同时大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。换句话说:方法区是一种规范,永久代是Hotspot针对这一规范的一种实现。而永久代本身也在迭代中:

在Java 6中,方法区中包含的数据,除了JIT编译生成的代码存放在native memory的CodeCache区域,其他都存放在永久代;
在Java 7中,Symbol的存储从PermGen移动到了native memory,并且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内);
在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),‑XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。

对于Java8, HotSpots取消了永久代,那么是不是也就没有方法区了呢?当然不是,方法区是一个规范,规范没变,它就一直在。那么取代永久代的就是元空间。它与永久代有什么不同的?

存储位置不同,永久代是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存;

存储内容不同,元空间存储类的元信息,静态变量和常量池等并入堆中。相当于永久代的数据被分到了堆和元空间中。

二、方法区里存着什么?

既然永久代是方法区的一种实现,那么在Hotspot下,方法区就等于永久代,也被称为非堆。那方法区里都存着什么呢?先抛结论:

静态变量 + 常量 + 类信息(构造方法/接口定义) + 运行时常量池存在方法区中 。

类信息与类常量池

方法区里的class文件信息包括:魔数,版本号,常量池,类,父类和接口数组,字段,方法等信息,其实类里面又包括字段和方法的信息。
在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值,参见下图。

因此class文件信息和class文件常量池的关系如下图:

图中还包含一个运行时常量池,这个稍后会介绍。

class文件常量池中存储了哪些内部呢?

我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);

每个class文件都有一个class常量池。

动态常量池

运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。

静态常量池和动态常量池的关系以及区别

静态常量池存储的是当class文件被java虚拟机加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。

动态常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。

本节总结

方法区里存储着class文件信息和动态常量池,class文件的信息包括类信息和静态常量池。可以将类的信息是对class文件内容的一个框架,里面具体的内容通过常量池来存储。

动态常量池里的内容除了是静态常量池里的内容外,还将静态常量池里的符号引用转变为直接引用,而且动态常量池里的内容是能动态添加的。例如调用String的intern方法就能将string的值添加到String常量池中,这里String常量池是包含在动态常量池里的,但在jdk1.8后,将String常量池放到了堆中。

三、jvm中的常量池

在Java的内存分配中,总共3种常量池:

字符串常量池(String Constant Pool):

字符串常量池在Java内存区域的哪个位置?
在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;

在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。

字符串常量池是什么?

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。

在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;

在JDK7.0中,StringTable的长度可以通过参数指定:

-XX:StringTableSize=66666

字符串常量池里放的是什么?

在JDK6.0及之前版本中,String Pool里放的都是字符串常量;

在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。关于String在内存中的存储和String#intern()方法的说明,可以参考我的另外一篇博客:

需要说明的是:字符串常量池中的字符串只存在一份!

如:

String s1 = "hello,world!";
String s2 = "hello,world!";

即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2.

class常量池(Class Constant Pool):

我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);

每个class文件都有一个class常量池。

运行时常量池(Runtime Constant Pool):

运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用

JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。

(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

参考文档

https://www.zhihu.com/questio...

http://blog.csdn.net/vegetabl...

https://www.cnblogs.com/holos...

https://blog.csdn.net/vegetab...

https://blog.csdn.net/u011635...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74388.html

相关文章

  • PostCSS 是个什么东西?

    摘要:参考的文章最权威的初认识一个不错的东西理解等插件解析技术大漠的系列文章,应用型很强,推荐一个年龄差不多的开发者的解释一篇稍微理论化解释的文章一个开发模式简单的优劣比较大家也可以直接阅读我的博客 前言 最近大漠前辈在群里发关于PostCSS的系列文章,但是耗子姐姐又说看了有点云里雾里的感觉,所以这篇文章将按一个思考的角度来理解一下 PostCSS 到底是一个什么东西。 showImg(h...

    IamDLY 评论0 收藏0
  • 记一次云服务器攻击事件

    摘要:收到报警年月号,正当大家热情的讨论人类首次拍摄的关于黑洞的照片的时候,突然收到了来自阿里云的报警,说是我们的一台服务器正在对外攻击赶紧登陆云控制台,看看到底是怎么回事。收到报警 2019年4月10号,正当大家热情的讨论人类首次拍摄的关于黑洞的照片的时候,突然收到了来自阿里云的报警,说是我们的一台服务器正在对外攻击!赶紧登陆云控制台,看看到底是怎么回事。 十有八九是中招了。。。登陆到服务器上去...

    wwolf 评论0 收藏0
  • PHP的yield是个什么玩意(一)

    摘要:这个关键字到底返回的是什么我们简单看一下返回的是一个叫做中文名就是生成器的对象,而这个生成器是实现了接口至于接口,你们去手册上搜索吧。 其实,我并不是因为迭代或者生成器或者研究PHP手册才认识的yield,要不是协程,我到现在也不知道PHP中还有yield这么个鬼东西。人家这个东西是从PHP 5.5就开始引入了,官方名称叫做生成器。你要说为什么5.5年代的东西,现在才拿出来。我还想问你...

    fxp 评论0 收藏0
  • 回调是个什么

    摘要:什么是回调首先一定要搞清楚回调的英文名什么是回电话假如你现在去一个商店买橘子,结果没有橘子了店员说,现在供货不稳定,不知道啥时候才能有橘子商店进货橘子商店进货橘子然后,你和店员说,这样吧,留个电话号码给你,橘子一到货,你就回个电话给我你的电 1.什么是回调? 首先一定要搞清楚回调的英文名 —— callback什么是callback?回电话 假如你现在去一个商店买橘子,结果没有橘子了 ...

    DevWiki 评论0 收藏0
  • 作为我的的第一门语言,学习Java时是什么感受?

    摘要:作为技术书籍或者视频,讲解一门语言的时候都是从最底层开始讲解,底层的基础有哪些呢首先是整个,让我们对这门语言先混个脸熟,知道程序的基本结构,顺带着还会说一下注释是什么样子。 2018年新年刚过,就迷茫了,Java学不下去了,不知道从哪里学了。 那么多细节的东西,我根本记不住,看完就忘。 刚开始学习的时候热情万丈,持续不了几天就慢慢退去。 作为技术书籍或者视频,讲解一门语言的时候都是...

    isaced 评论0 收藏0

发表评论

0条评论

binaryTree

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<