1,java内存模型简介
《深入理解java虚拟机》里将java内存分为如下五个模块:
堆-所有线程共享的,主要用来存储对象。
其中,堆可分为:新生代和老年代两块区域。使用NewRatio参数来设定比例。对于年轻代,一个Eden区和两个Suvivor区,使用参数SuvivorRatio来设定大小;
Java虚拟机栈/本地方法栈-线程私有的,主要存放局部变量表,操作数栈,动态链接和方法出口等;
虚拟机栈
每个java方法在执行时,会创建一个“栈帧(stack frame)”,栈帧的结构分为“局部变量表、操作数栈、动态链接、方法出口”几个部分(具体的作用会在字节码执行引擎章节中讲到,这里只需要了解栈帧是一个方法执行时所需要数据的结构)。我们常说的“堆内存、栈内存”中的“栈内存”指的便是虚拟机栈,确切地说,指的是虚拟机栈的栈帧中的局部变量表,因为这里存放了一个方法的所有局部变量。
方法调用时,创建栈帧,并压入虚拟机栈;方法执行完毕,栈帧出栈并被销毁,如下图所示:
本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。
不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。如何去服务native方法?native方法使用什么语言实现?怎么组织像栈帧这种为了服务方法的数据结构?虚拟机规范并未给出强制规定,因此不同的虚拟机实可以进行自由实现,我们常用的HotSpot虚拟机选择合并了虚拟机栈和本地方法栈。
pc寄存器(程序计数器)-同样是线程私有的,记录当前线程编译class文件的行号指示器,为线程之间的切换提供保障;
2,永久代和方法区
涉及到内存模型时,往往会提到永久代,那么它和方法区又是什么关系呢?《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 同时大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。因此,我们得到了结论,方法区是在Java虚拟机规范中的定义,是一种规范,永久代仅仅是HotSpot的概念,是一种实现,一个是标准一个是实现。其他的虚拟机实现并没有永久带这一说法。在1.7之前在(JDK1.2 ~ JDK6)的实现中,HotSpot 使用永久代实现方法区,使用 GC分代来实现方法区内存回收。
方法区-线程共享的,主要存储类信息、运行时常量池、静态变量、JIT编译后的代码 字面量和符号引用等数据。方法区理论上来说是堆的逻辑组成部分;
3,元空间
java 8中废除了永久代的概念,取而代之的是元空间。为什么叫元空间,是因为这里面存储的是类的元数据信息。什么又叫元数据信息?元数据就是数据的数据,或者说是描述数据的数据,信息的信息。
永久代和元空间的区别:
元空间是直接存在内存中的,并不在java虚拟机中,因此元空间依赖于内存大小。当然也可以自定义元空间大小。
永久代理论上是堆的一部分,和新生代老年代的地址是连续的。
元空间存储类的元信息,而将静态变量和常量池等并入堆中。相当于原来永久代的数据被分到了堆和元空间中。
4,class文件常量池
Class 文件常量池指的是编译生成的 class 字节码文件,其结构中有一项是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
这里的字面量是指字符串字面量和声明为 final 的(基本数据类型)常量值,这些字符串字面量除了类中所有双引号括起来的字符串(包括方法体内的),还包括所有用到的类名、方法的名字和这些类与方法的字符串描述、字段(成员变量)的名称和描述符;声明为final的常量值指的是成员变量,不包含本地变量,本地变量是属于方法的。这些都在常量池的 UTF-8 表中(逻辑上的划分);
符号引用,就是指指向 UTF-8 表中向这些字面量的引用,包括类和接口的全限定名(包括包路径的完整名)、字段的名称和描述符、方法的名称和描述符。只不过是以一组符号来描述所引用的目标,和内存并无关,所以称为符号引用,直接指向内存中某一地址的引用称为直接引用;
5,运行时常量池
运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。
6,字符串常量池
字符串常量池是全局的,JVM 中独此一份,因此也称为全局字符串常量池。运行时常量池中的字符串字面量若是成员的,则在类的加载初始化阶段就使用到了字符串常量池;若是本地的,则在使用到的时候(执行此代码时)才会使用到字符串常量池。其实,“使用常量池”对应的字节码是一个ldc指令,在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用(jdk1.7)。String 类的intern()方法还可在运行期间把字符串放到字符串常量池中。JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术,但是 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在 [-128,127] 时才会使用缓冲池,超出此范围仍然会去创建新的对象。其中:
在 jdk1.6(含)之前也是方法区的一部分,并且其中存放的是字符串的实例; 在 jdk1.7(含)之后是在堆内存之中,存储的是字符串对象的引用,字符串实例是在堆中; jdk1.8 已移除永久代,字符串常量池是在本地内存当中,存储的也只是引用。
转载至: https://www.2cto.com/kf/201809/779764.html