本篇文章带来的是对Java内存数据模型的介绍,这对于我们深入理解Jvm虚拟机工作的原理和Java内存的划分大有裨益,好了,为了让我们理解的更为深刻,我们将会加入图片辅助的方法去理解。
本篇博文的目录:
一:Java内存数据模型的介绍
二:线程私有内存
三:程序计数器
四:Java虚拟机栈
五:本地方法栈
六:Java堆
七:方法区
八:运行时常量池
九:总结
一:java内存数据模型的介绍
java程序在运行的时候会在内存中开辟不同的空间用以管理不用的内存区域,每个区域都有自己的功能,创建和销毁时间,有的区域会随着虚拟机的启动而创建,而有的区域会随着用户线程的创建而销毁。按照空间,分为如下不同的空间,接下来我们将按照不同的区域进行学习,学习它不同区域的功能和用法
图 1-1 :java内存数据模型
二:线程私有内存
我们先来看一下多线程运行原理:其实cpu在运行的时候每次在同一特定的时间点只能运行一个线程(单核的情况下),只不过它切换的速度非常之快,让我们觉的它是在进行多线程运行,本质上它还是单一运行的。所以这就会必然引出一个问题:cpu切换线程如何保证它自身的运行不受其他的线程影响,保证每个线程都是独立不受外部侵扰的,这就产生了线程私有内存这个概念,主要是维持线程的安全、稳定、高效的运转。关于这一点也很好理解,比如我们去一个快速餐厅吃饭,我们点好了菜,会产生菜单小票,这个小票上面有编号,就是每个线程的“私有内存”,服务员再进行不同顾客上菜的时候就有了区分度,这样就可以顺利找到不同的顾客(切换不同的线程);在java线程中,程序计数器、虚拟机栈、本地方法栈都属于线程私有的。
注意点:线程私有不存在多线程并发的资源竞争问题,因为其享有的内存是互不影响的,不存在并发问题
与线程私有相对,就有线程公有内存,这个区域在jvm虚拟机中有个特定的称呼叫做:主内存
三:程序计数器:
对应于图上标记的深红色部分,主要是指程序在运行过程中所执行的字节码(.class)文件的行号指示,也称作行号指示器,程序流程的分支、循环、异常处理等基础功能都需要它的引导来完成。它占有的内存空间比较小,它的运行的原理是:通过改变计数器的值来选取下一条的需要执行的字节码命令,而这个值具体指的是虚拟机在Java方法中字节码指令的地址,但是如果执行的是Native方法,那么它的计数器的值是为空的。
注意:
1:它是java中唯一一个没有内存溢出(out of memory)情况的区域。可以思考一下这点是为什么?它是进行线程运行的指示灯,如果没有了它,程序也就无法运行了。
2:它属于线程私有的:每个线程内部都有一个程序计数器,为了保证每个线程切换前后都能正常运行。
四:Java虚拟机栈:
虚拟机栈主要是作用在java方法运行时候,每个线程在运行到一个方法的入口都会创建一个栈帧,创建栈帧的目的在于存储局部变量、操作数栈、动态链接、方法出口等信息。按照这个原理,那么一个方法在从调用直到完全执行完毕,都会对应一个从入栈到出栈的过程。
这里我们说明一下局部变量,它的含义就是定义在java方法内部中的变量,对应于java中8大基本数据类型,byte、int、float、boolean、char、short、long、reference:(long和double占用2个空间,其余1个)局部变量表里我们需要注意的是,它在编译期已经完全了内存分配,这样栈帧在用局部变量的时候,它占用的内存是已经确认的,不需要再分配,这就一定程度上减少了栈帧的工作量。
注意:
1:当线程请求的栈帧的深度大于虚拟机允许的深度,就会抛出stackOverFlow异常
2:我们平时所说的堆栈,其中的栈就是指的是这里的栈帧
3:它也是线程私有的,并且和线程的生命周期相同。
图 2-1:虚拟机栈
五:本地方法栈:
本地方法栈,顾名思义它代表的就是本地方法Native执行的栈帧,在jdk的源码中我们可以看到很多命名为Native的方法,Native方法很大一部分是采用C/C++写的,但是它对Native采用的数据结构、语言等都没有做具体的要求,这一点完全是由java虚拟机进行实现的。
注意:
1:这个区域会抛出oom或者sof(stackoverflowError)异常
2:同样它属于本地线程私有的
六:Java堆:
Java堆主要是存储对象的地方,同时它也是java内存管理区域最大的一块。当我们在程序中new一个对象出来或者新建一个数组,就会把对象存储这个区域。它是java的最重要的公共区域之一,与线程私有相对,它是属于线程公有的。基本上所有的对象都会存储在此区域,但也不是绝对的(随着JIT技术的成熟,这一点会发生微变)。堆是主要存放对象的地方,同时它就会产生另一个问题就是GC,当垃圾进行回收的时候,虚拟机并没有开辟新的空间,还在此区域进行,同时这个区域也叫“GC堆”,再细致划分下去,分为新生代、老年代,同时还分为Eden空间、From survivor空间、To Survior区域。为什么要划分的这么细致呢?其实还是主要还是为了更加有效率的回收,划分的区域越细致,那么垃圾回收器收集的时候只要去对应的地方直接回收就行,不用加上额外的判断逻辑。
注意点:
1:堆在逻辑上是连续不间断的内存空间,但是在物理上可以是不连续的内存空间
2:在堆中如果没有完成内存的实例分配就会抛出oom
3:java堆是线程公有的,所有的线程共享这一片区域
图 3-1 java堆内存
七:方法区
方法区主要是用来存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,比如我们在代码中定义的Constant常量就会在这个区域存储。在java虚拟机规范中,它是属于堆的逻辑部分。同时在这个区域中,它也会有垃圾回收器工作,这个区域叫做“永久代”,之所以叫做永久代,因为它比新生代和老年代拥有更长的生命周期,但是并不是在这个区域它就会万事大吉了,永久代依然会存在垃圾回收的情况,只不过相对来说较少
注意点:
1:此区域属于线程公有的,线程的类信息、常量、静态变量、编译代码都在此区域进行存储
2:此区域会抛出oom异常,发生在方法区无法进行内存分配时
图:4-1 方法区
八:运行时常量池:
运行时常量池主要是方法区的一部分,class文件除了有类的版本、字段、方法接口等描述信息外,还有一项信息是常量池,用于存储编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区运行时常量池中存放。
注意:此区域同样会发生oom异常
九:总结
本篇博文主要是对java的内存数据模型区域划分进行了简单的介绍,没有深入细致过多的介绍。不过我们在整体上有一个感性的认识,理解java内存区域的划分,以及这样的划分的好处,还有设计到线程的部分,理解并发的问题。这部分属于虚拟机底层的东西,属于进阶。相信看完这篇文章后,我希望能回答以下几个问题。同时这些东西也是考究我们对于底层的认识,面试中也是经常会被提及到的东西
1:java内存模型分为哪几个区域?
2:请说出属于线程私有部分和线程公有部分的区域?
3:我们都知道内存是在按照“代”进行划分的,那么请问java堆中内存分为哪几代?分别位于什么区域?
4:为什么要把java内存划分这么多,划分这么多不嫌麻烦吗?有什么好处
我希望能思考以上问题,学习的时候就要进行多思考,深入思考这是为什么?这篇博文的介绍就到此结束了,下一篇见,对于java内存模型的理解有助于我们处理并发的问题,这是属于javaEE中高端进阶必不可少的基础,在此留个记录,同时分享出来,让大家也能清楚的认识Java内存模型同时提醒自己深入理解。
主要参考资料:
《深入Jvm虚拟机》