Java内存区域——直接内存和运行时常量池

运行时常量池是属于方法区的一块,class文件中除了有类的版本、字段、方法、接口等描述信息以外,还有一项信息就是常量池,那么,这个常量池是干什么的呢?它就是用来存放编译期生成的各种字面量以及符号引用这部分内容将在类加载后,进入方法区的运行时常量池中存放。举个例子

Java内存区域——直接内存和运行时常量池_第1张图片

基本数据类型抽象数据类型的引用会放到哪里呢?我们之前所讲的内存区域,随着这个方法的运行,会对每一个方法创建一个栈帧,栈帧里面有一个局部变量表,那么,我们所有的基本数据类型,包括抽象数据类型的引用都会放到局部变量表中。String是一个抽象数据类型,那么s1就是存放的引用,s1和s2这两个引用分别存到一个局部变量表中,那么,“abc”到底放到哪里了呢?“abc”是一个对象,是一个引用类型,那么也就是说,“abc”应该按照我们的理解分配到Java堆中,如果是分配到Java堆中的话,其实我们说在创建对象实例的时候才会往堆中去分配空间,“abc”就是s1对象的实例,我们认为,如果说它每一次都创建了实例的话,每一次创建实例,每一次都会往堆中去新开辟一块空间来存放这个对象实例的数据

Java内存区域——直接内存和运行时常量池_第2张图片

如果说,按照我们刚才说的,每一次创建一个实例,都需要在堆中开辟一块空间,那么,“==”比较的是地址,比如这是我们的堆内存

Java内存区域——直接内存和运行时常量池_第3张图片

还有一块栈内存

Java内存区域——直接内存和运行时常量池_第4张图片

栈内存里面有一块小区域局部变量表,局部变量表中有两个变量,一个是s1,一个是s2Java内存区域——直接内存和运行时常量池_第5张图片

s1和s2分别引用堆内存中的,我们说两个实例,每一次都会创建一个实例

Java内存区域——直接内存和运行时常量池_第6张图片

那么,s1和s2是不相等的。Java中虽然没有指针,但是实际上它的引用也就相当于引用了一个地址,只是对于我们开发者来讲是隐式的。

Java内存区域——直接内存和运行时常量池_第7张图片

这就验证了我们这里的说法是错误的

Java内存区域——直接内存和运行时常量池_第8张图片

因为我们讲的是常量池,而这里说的却是堆的问题,所以,它肯定不是在堆中去创建,那怎么回事呢?这里就说到了常量池,任何一个字符串的创建,都会扔到常量池中常量池是在方法区中的一块存储空间,比如这是我们的方法区

Java内存区域——直接内存和运行时常量池_第9张图片

在方法区中有一块叫做运行时常量池

Java内存区域——直接内存和运行时常量池_第10张图片

运行时常量池中其实维护了,比如说我们可以想象,它里面有一个StringTable,字符串表,那么,它的数据类型我们可以想象成是一个HashSet,有这么一个数据结构

Java内存区域——直接内存和运行时常量池_第11张图片

来存放我们所实例化的字符串对象,来一个“abc”,往这个HashSet集合中扔一个,再来一个“abc”,再往这个HashSet集合中扔一个

Java内存区域——直接内存和运行时常量池_第12张图片

那么,到这里大家就明白了,我们知道HashSet有一个特征,无序且不可重复,“abc”和“abc”显然是相等的,显然不会存在两个,只会有一个“abc”,也就是说,只会存进去“abc”这么一个实例,那么,既然是只创建了一个实例,那么,这一个实例的地址显然是相同的,所以,s1==s2。

我们看一下s3

Java内存区域——直接内存和运行时常量池_第13张图片

我们又创建了一个实例,只不过这个创建的方式是由我们手动的new创建的,那这个时候,s1和s3会不会相等呢?我们刚才说,字符串都是扔到常量池中,都会扔到一个类似于HashSet这么一个数据结构中。字符串“abc”会在常量池中的HashSet中找,发现有“abc”了,就返回一个指针,让它指向这个“abc”就可以了。那么,也就是说s1和s3应该是相等的

Java内存区域——直接内存和运行时常量池_第14张图片

为什么s1不等于s3呢?因为,我们通过new来创建对象的话,那么,它一定是在堆内存,它就不再去考虑常量池的问题了,就直接的在堆内存中开辟一块空间,直接把这块空间的地址赋值给s3,所以,new的“abc”在堆中字符串对象“abc”在常量池中,显然不是同一块内存区域,那么,内存地址肯定是不相同的,所以,s1==s3打印的是false。

Java内存区域——直接内存和运行时常量池_第15张图片

Java内存区域——直接内存和运行时常量池_第16张图片

那么,这个intern又是一个什么东西呢?为什么加上它之后就变成了true?intern()这个方法,它会把堆中的这块区域

Java内存区域——直接内存和运行时常量池_第17张图片

搬到方法区中的运行时常量池中,这里也就提到了运行时常量池。

Java内存区域——直接内存和运行时常量池_第18张图片

其实我们发现,这些,就是相当于我们定义了一个字符串,其实就是定义了一个字符串的常量,它的值是不会改变的,那么,它就相当于已经进入到了常量池,既然是常量了,它值是不可改变的,不可改变,那么肯定是在我们代码中去声明的,去定义的,那么,运行时怎么还会产生常量呢?其实

Java内存区域——直接内存和运行时常量池_第19张图片

这就是运行时所产生的常量,我们称

Java内存区域——直接内存和运行时常量池_第20张图片

这种常量,我们可以称之为它叫字节码常量

Java内存区域——直接内存和运行时常量池_第21张图片

这个我们就可以称之为运行时常量,其实运行时也是会产生常量的。当然了,不光使用intern()这种定义,做一个字符串的拼接,拼接后就产生一个字符串常量。可以认为intern()就是

Java内存区域——直接内存和运行时常量池_第22张图片

把这个值拿出来,扔到

Java内存区域——直接内存和运行时常量池_第23张图片

运行时常量池中,产生一个运行时常量,因为运行时常量池中已经存在“abc”了,那么在进行s1==s3.iintern()比较的时候,其实s3.intern()拿的就是运行时常量池的中“abc”,再和s1比较,显然就是相同的。那么,我们可以想象一下intern()方法是如何实现的,你用Java代码能不能实现呢?把

Java内存区域——直接内存和运行时常量池_第24张图片

这块区域中的数据扔到

Java内存区域——直接内存和运行时常量池_第25张图片

中。

其实,我们想象不到是怎么实现的,我们来看一下intern()方法的实现

Java内存区域——直接内存和运行时常量池_第26张图片

发现,我们是看不了的,我们Java代码确实不容易实现,使用的是一个native方法,本地方法

运行时常量池是方法区中的一部分,受到方法区内存的限制,如果常量池无法申请到内存时,同样会抛出OutOfMemoryError。

下面我们看直接内存。

直接内存并不是虚拟机运行时数据区的一部分,它也不是Java虚拟机规范中所定义的一块内存区域,但是,它却是确确实实存在的这么一块内存区域,也被我们频繁的使用。在JDK1.4的时候,新增了一个弥补IO性能的这么一个叫做NIO,引入了一种有缓冲区的IO方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆中的drectbytebuffer对象作为这块内存的引用,进行操作,也就是说它能够分配堆外内存,就不受制于Java虚拟机内存的制约,其实也是为了提高性能,它虽然不受Java虚拟机内存的制约,但是它会受到我们当前操作系统物理内存的制约,所以,当如果申请不下来内存,直接内存这一块区域也会抛出OutOfMemoryError,当出现这个异常的时候,我们要想到还有直接内存的存在,关于直接内存我们就说这么多,其实它非常简单,而且我们也不会太多的关注它,主要就是在我们像NIO中,直接分配堆外内存所使用的这么一块内存,就是所谓的直接内存。其他地方几乎是不用的。

你可能感兴趣的:(JVM)