在开始讲java内存模型之前,首先我们先来了解一下关于内存方面的介绍:

java波动数据分析 java数据分析模型_jmm

在我们的程序执行中,需要不断的根据逻辑地址和物理地址之间进行映射,找到指令具体执行的位置。

Java程序运行在虚拟机之上,运行的时候也是需要内存空间的,在执行java程序的过程中,JVM内部会将整个jvm划分成为不同的数据区域去管理。

我们都知道,c的编译器会在划分内存区域的时候,经常将管理的区域划分成数据段和代码段。数据段分为堆,栈和静态数据区,那么在java语言中,内存区域又是如何划分的呢,我们可以从线程的角度和模型的角度去分析这个问题 :

  • 线程的角度

即哪些是线程私有的区域,哪些是线程共享的区域:


java波动数据分析 java数据分析模型_java波动数据分析_02

  1. 程序计数器(Program Counter Register)

程序计数器在java内存中占据一段较小的内存模型,它是当前线程所执行的字节码行号指示器(逻辑),并且它可以在改变计数器的值来选取下一条需要执行的字节码指令,由于多线程的原理还是通过轮换线程分配cpu的资源去进行高效执行,所以它和线程是一对一的关系。

如果一个线程正在执行的是一个java方法那么程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,这个程序技术器的值为Undefined,此外,由于即使记录行号,所以不用担心内存泄露的问题。

最后,程序计数器本质上是逻辑计数器而不是物理计数器,为了程序切换线程之后都能正确的回复位置,所以每一个线程都有一个独立的程序计数器,并且永远只为java方法计数

  1. Java虚拟机栈(Stack)

java虚拟机栈也是线程私有的,它是Java方法执行的内存模型,每个方法被执行的时候都会产生一个栈帧用来存储着方法,当方法开始执行的时候,就入栈,方法被垃圾回收了之后就出栈。


java波动数据分析 java数据分析模型_Java_03

我们可以看见在一个栈帧中存储着局部变量表,操作栈,动态链接和返回地址等信息,而局部变量表和操作数栈也是有一定区别的,在这里对这两个栈帧中相对关键的部分进行一下简单的介绍:

局部变量表:包含方法执行过程中的所有变量。
操作数栈:包含入栈,出栈,复制,交换,产生消费变量。

这里做一个例子做对应的解释参考:

java波动数据分析 java数据分析模型_java波动数据分析_04

最后,我们再根据刚才的讲解提出一个问题,为什么我们在递归的时候会引发java.lang.StackOverflowError异常,我们先在程序中,手动创造一下这个异常:

java波动数据分析 java数据分析模型_java波动数据分析_05

那么,为什么会发生这个Stack Overflow异常呢?

当线程执行一个方法的时候,就会对应的创建一个栈帧,并且将栈帧压入虚拟机栈中,当方法结束的时候,就会将栈帧出栈,因此,我来描述一下,每次递归,会调用一个方法,生成一个栈帧,而递归没有结束,所以方法也还保存在虚拟机栈上,如果超过了一定限度,就会栈帧溢出,最后就堆栈溢出了。

此外,当虚拟机栈过多的时候还会引起OOM异常:

当我们递归的同时,虚拟机栈也不是死的,当发现在即快撑不下的时候,会去申请新的内存空间,但是如果没有新的内存空间再去给你申请,就会oom了。

举个其他的例子:


java波动数据分析 java数据分析模型_java波动数据分析_06

按理说就会报出:java.lang.OutOfMemoryError:unable to create new native thread

这时候系统处于假死状态。

  1. 本地方法栈(native Stack)

和java虚拟机栈相似,主要作用是标注了native的方法

  1. 元空间(MetaSpace)与永久代(PermGen)的区别

在jdk8开始把类的元数据放在本地内存中,这个部分也就叫做MetaSpace元空间。这个区域在之前的jdk都是属于永久代的,元空间和永久代都是存储class的相关信息,包括class对象的Method,实际上元空间和永久代都是之前说过的方法区的实现,只是实现有所不同,所以说方法区只是一种jvm的规范,在之后的jdk版本中,字符串常量池已经被移动到了Java堆中,使用元空间替换掉了永久代

元空间和永久代最大的区别在于,元空间使用的是本地内存,而永久代使用的是JVM的内存,那么使用本地内存有什么好处呢,最大的好处就是,我们在jdk8及之后都不会出现oom:PermGen space这个异常,因为内存只会受到本地内存大小的限制了。

但jvm也不会无限去使用本地内存,而是动态设置的。

其中,MetaSpace比PermGen的优势在于:

(1)字符串常量池存在永久代中国,容易出现性能问题和内存溢出
(2)类和方法的信息难易确定,给永久代的大小指定带来困难
(3)永久代会为GC带来不必要的复杂性,并且回收效率偏低
(4)元空间可以更加方便HotSpot与其他JVM如Jrockit的集成

  1. Java堆(Heap)

对于大多数应用来讲,Java堆是Java内存管理中最大的一个区域,是所有内存共享的一块区域,其有一个非常关键的作用就是存放类的实例,所有的对象实例都在堆里面分配内存

java波动数据分析 java数据分析模型_jmm_07

如果堆没有足够内存分配给对象实例,就会抛出异常,oom

同时堆也是GC管理的主要区域,因此外号也可以称为GC堆。 并且Java堆也采用了分代垃圾回收算法,也就分为了新生代(Eden)和老年代(tenured)

java波动数据分析 java数据分析模型_程序计数器_08

GC会在后面的文章进行笔记的整理。