Java内存区域和内存模型

大纲

前言


个人全部文章同步更新与Github--Java-Notes,想了解JVM,HashMap源码分析,spring相关,剑指offer题解(Java版),能够点个star。能够看个人github主页,天天都在更新哟。html

邀请您跟我一同完成 repojava


Java内存区域(内存结构)是面试的时候,问到JVM相关必定会问的东西,可是不少人有些概念是搞混的git

重要

阅读这篇文章前,一个重要的概念要弄清楚。github

Java 内存区域和 Java 内存模型(JMM)是两个概念面试

不少人,包括写博客的一些人,他们本身都搞不清楚这两个啥意思,我搜出来文章不少将二者混为一谈的。算法

我用这个名字作标题也是让不少不太清楚的朋友都能搜到这篇文章,而后搞清楚概念。这篇文章讲 Java内存区域,若是你是想了解内存模型的话,能够看个人这篇文章 Java内存模型。固然若是你是面试的话,通常是问你Java内存区域(JVM运行时数据区),因此我先写这一篇文章spring

Java内存区域

Java内存区域是这样的:数据结构

内存模型

内存模型是这样的多线程

两者关系

引用《深刻理解Java虚拟机》里的原话:"这里所讲的主内存、工做内存与本书第2章所讲的Java内存区域中的堆,栈、方法区等并非同一层次的内存划分,这二者基本上是没有关系的,若是二者必定要勉强对应起来,那从变量、主内存、工做内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分,而工做内存则对应于虚拟机栈中的部分区域"函数

内存区域划分

Java运行时数据区能够分为两类:

  • 线程私有
    • 虚拟机栈
    • 本地方法栈(不是全部JVM都有,好比Hotspot就没有)
    • 程序计数器
  • 线程共享
    • 方法区

线程私有

虚拟机栈

概念

java虚拟机栈是线程私有的,他的生命周期和线程相同。他描述的是Java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧用于存储局部变量表、操做数栈、动态连接、方法出口等信息每一个方法从调用知道执行完的过程,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程

什么意思呢?我也用他的这个调用方法入栈的方式给你讲吧

栈帧

他存放了如下重要信息(部分):

  • 局部变量表
  • 操做数栈
  • 动态连接
  • 方法出口
  • 等……..

咱们还记得上面图的内存模型吗?咱们把Java线程单独拿出来,他的数据结构就成了这个样子

假设咱们如今有一个递归程序

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.recursive();
    }
    public void recursive(){
        recursive();
    }
}

复制代码

如今他在main线程中执行,那么你能够把上图的当前线程替换成main线程,而后他每调用一次recursive函数,他就把它压入栈中。若是当前执行方法完,那么他的栈帧就要出栈被抛弃。

上面的不会执行完,他会一直调用,直到消耗栈容许的最大深度,而后抛出 Stack Overflow异常,这个我在后面异常的时候会讲到

slot

咱们还听到不少博客讲述这个区域的时候有用到 slot这个概念,那个这个slot是什么呢?

咱们刚刚说到栈帧的数据结构,他存储的重要信息,其中有一个是局部变量表,而这个局部变量表也是由一个个小的数据结构组成,他就叫局部变量空间(slot)。你也能够把它当成是一个空间大小的度量单位,就像字节同样,由于存储的数据的大小也是用这个衡量的。

局部变量表存放了编译期可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型。(我不知道书上是否是写错了,由于下图的本地变量表中并无这个类型,并且在上图的栈帧中,也是把这个和局部变量表分开列出的,若是知道答案的请给我留言,很是感谢)

局部变量表,就以下图所示(这个是对象的访问定位的其中一种方法,指针直接访问对象,Java采用的是这种)。若是要看Java对象访问的话,能够参考个人这篇文章,Java对象访问。

咱们看到 intshort等只占用了一个 slot可是double占用了两个slot,(long没有画出,但他也占用两个slot)在java虚拟机中确实是这样,double和long类型比较特殊,在其余方面,存储long和double的时候,别的数据通常使用一个单位的空间就行,他们就须要两个。

异常

虚拟机栈会抛出两种异常

  • Stack Overflow Error
  • OutOfMemoryError (也就是常说的OOM异常)

这两种异常是从两个方面说的,能够形象的说为 深度和宽度

还记得上面栈帧的图和个人程序吗?

当我启动那个程序他必定会报出 Stack Overflow Error。 由于他是无限的递归,可是个人虚拟机的深度是有限的(这个能够进行参数设置调整 分配参数:-Xss)。顺便说一句,若是你要运行不少的线程,那么你可能须要把栈的深度调小,由于总的就那么大,而每一个线程使用的是独立大小

OOM异常的话,是因为若是虚拟机栈能够动态拓展的话(大部分均可以扩展),若是拓展时没法申请到足够的空间,那么就会抛出OOM异常

本地方法栈

概念

注意:有的虚拟机中是将本地方法栈和虚拟机栈合在一块儿的,好比Hotspot虚拟机

本地方法栈和虚拟机栈差很少,不过 Java虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机使用到的Native方法服务。咱们知道Java虚拟机有一部分是用其余语言编写的,好比C/C++,所以他有一部分是这些语言的类库方法。

Navtive 方法是 Java 经过 JNI 直接调用本地 C/C++ 库,能够认为是 Native 方法至关于 C/C++ 暴露给 Java 的一个接口,Java 经过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会建立一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态链接并直接调用指定的 native 方法。

具体能够参考这篇博文 Java本地方法栈

异常

由于和Java虚拟机栈相似,因此异常也是

  • Stack Overflow
  • OOM

程序计数器

概念

程序计数器是线程私有的,他是一块较小的内存空间,他能够看做是当前线程执行的字节码的行号指令器。在虚拟机的概念模型里(仅仅是概念模型,不一样虚拟机的实现方式可能有更高效的方法),字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理等基础功能都须要依赖这个计数器。

为何线程私有

Java虚拟机的多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器(对于多核处理器来讲是一个内核)都只会执行一条线程中的指令、所以为了线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为"线程私有"的内存。

特殊

他是Java虚拟机规范中惟一一个没有规定OOM异常状况的区域

线程共享

这里是GC垃圾回收重点"照顾"的对象,能够参考个人博文:JVM垃圾回收

Java堆

概念

对于大所数应用来讲,Java堆是Java虚拟机所管理的内存中最大的一块,也被线程共享。惟一的目的就是存放对象实例,几乎全部的对象实例都在这里分配内存

他还能够再细分为:新生代,老年代(基于分代垃圾回收算法,能够参考我上面的博文链接)

异常

  • OOM

能够经过参数设置大小(-Xmx 和 -Xms)

方法区

概念

和Java堆同样是线程共享的,它用于存储已经被虚拟机加载的:

  • 类信息
  • 常量
  • 静态变量
  • 即时编译后的代码
  • 等…...

永久代

使用 Hotspot 虚拟机的开发者来讲,更习惯把方法区称为"永久代",可是二者并不等价,仅仅是Hotspot虚拟机的设计团队选择把GC分代收集扩展到方法区。并且**永久代已经从JDK1.7开始逐渐移除,并在 JDK1.8以后彻底被移除。**并且Hotspot之因此有这样的状况是由于当时他们为了方便,懒得为方法区单独写内存管理算法,而直接使用Java堆的管理算法。

而后他们就为这个付出了代价,因此如今已经移除了这个永久代

异常

  • OOM

当方法区没法知足内存分配需求

总结

  • java内存区域(或者成为内存结构或者运行时数据区)和java内存模型(JMM)不是同一个概念

  • 内存区域划分

    • 线程私有

      • Java虚拟机栈
      • 本地方法栈(不是每一个JVM都有)
      • 程序计数器
        • 惟一一个没有OOM异常的地方
    • 线程共享

      • Java堆
      • 方法区
        • 永久代不等同方法区,在JDK1.8已经被移除
相关文章
相关标签/搜索