Java内存模型---JMM

一、Java的内存模型JMM

1.JMM概述:

JMM主要是为了为了规定线程和内存之间的一些关系,根据JMM的设计,系统存在一个主内存,JAVA中所有实例变量都储存在主内存中,对于所有线程都是共享的,每条线程都有自己的工作内存,工作内存由缓存和堆栈两部分组成,缓存中保存的是主内存中变量的拷贝,缓存可能并不总和主内存同步,也就是缓存中变量的修改可能没有立刻写到主内存中,堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量

2.JMM是什么

JMM (Java Memory Model)是Java内存模型,JMM定义了程序中各个共享变量的访问规则,即在虚拟机中将变量存储到内存和从内存读取变量这样的底层细节。 为什么要设计JMM 屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

3.为什么要理解JMM

主内存,工作内存和线程三者的交互关系
JMM规定了共享变量都存储在主内存中.每条线程还有自己的工作内存,线程的工作内存保存了主内存的副本拷贝,对变量的操作在工作内存中进行,不能直接操作主内存中的变量.不同线程间无法直接访问对方的工作内存变量,需要通过主内存完成。

4.JDK内存模型

4.1JDK1.6内存模型
在这里插入图片描述
4.2JDK1.7内存模型

在这里插入图片描述
4.3JDK1.8内存模型
在这里插入图片描述

二、线程私有的内存区域

1.程序计数器

一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。

2. Java虚拟机栈

和线程相关,不同线程内,即使运行同一个方法,也是处于不同内存,和方法相关,即使是同一个线程,递归调用某个方法,每次调用都会生成该次调用的方法帧
每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。
之前我们一直讲的栈区域实际上就是此处的虚拟机栈,再详细一点,是虚拟机栈中的局部变量表部分。
此区域一共会产生以下两种异常:
2.1. 如果线程请求的栈深度大于虚拟机所允许的深度(-Xss设置栈容量),将会抛出StackOverFlowError异常。
2.2虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM内存溢出(OutOfMemoryError)异常

3.本地方法栈

本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的Java方法服务。

三、线程共享区域

1.Java堆

在JVM启动时创建,所有的对象实例以及数组都要在堆上分配 。如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM。

2.方法区/元数据区

用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。此区域的内存回收主要是针对常量池的回收以及对类型的卸载。当方法区无法满足内存分配需求时,将抛出OOM异常。

3.运行时常量池

编译期及运行期间产生的常量被放在运行时常量池中。 这里所说的常量包括:基本类型、包装类(包装类不管理浮点型,整形只会管理-128到127)和String。类加载时,会查询字符串常量池,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

4. 直接内存

在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。 直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。也可能导致OutOfMemoryError异常出现。

四、 内存溢出与内存泄漏

1.OOM:内存溢出

1.1概述:
指存在无法回收的内存过多,最终使得运行要用到的内存大于能提供的最大内存
结果:此时程序就运行不了,系统会提示当前内存溢出,有时候会关闭软件,重启电脑或者软件后释放掉一部分内存又可以使用了(进程都会挂掉情况严重)
1.2内存溢出原因:
1.2.1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
1.2.2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
1.2.3.代码中存在死循环或循环产生过多重复的对象实体;
1.2.4.使用的第三方软件中的BUG;
1.2.5.启动参数内存值设定的过小
1.3内存溢出的解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

2.内存泄漏

2.1概述:指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度缓慢甚至系统崩溃等严重后果
2.1.1内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。
2.1.2:一次内训泄漏似乎不会有大的影响,但内存泄漏后堆积的结果就是内存溢出。
2.1.3:内存泄漏具有隐蔽性,积累性的特征,比其他内存非法访问错误更难检测。这是因为内存泄漏产生的原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏不会直接产生可观察的错误,而是逐渐积累,降低系统的整体性性能。
2.1.4:如何有效的进行内存分配和释放,防止内存泄漏,是软件开发人员的关键问题,比如一个服务器应用软件要长时间服务多个客户端,若存在内存泄漏,则会逐渐堆积,导致一系列严重后果。
2.2一般出现内存泄漏的场景:
长生命周期存活的对象,内部持有不使用对象的引用,导致不使用的垃圾对象无法回收
2.3一般经常程序经常出现内存泄漏的例子:在使用长期存活的数据结构、数组时,都要考虑对应引用导致内存泄漏的问题
2.4内存泄漏的最终结果
可用内存越来越小,如果再下次申请的时候,不够了就抛OOM异常
2.5内存泄漏原因
2.5.1:在内存中供用户使用的空间分为三部分:
程序存储区
静态存储区:静态存储区数据在程序开始就已经分配好了内存,执行过程中,它们所占的存储单元是固定的,在程序结束时就 释放,所以该区数据一般为全局变量。
动态存储区:动态存储区数据是在程序的执行过程中根据需要动态分配和动态释放的存储单元。
2.5.2:没有释放动态分配的存储空间而造成内存泄漏,是动态存储变量的主要问题。
2.5.3:常用内存管理函数如下:malloc,free,calloc,recalloc等,来完成动态存储变量存储空间的分配和释放。
2.5.4:常见的内存管理错误如下
分配一个内存块并使用其中未经初始化的内容;
释放一个内存块,但继续引用其中的内容;
子函数分配的内存空间在主函数出现异常中断时,或主函数对子函数返回的信息使用结束时,没有对分配的内存进行释放;
程序实现过程中分配的临时内存在程序结束时,没有释放临时内存;
内存错误一般是不可在现的,开发人员不易在程序调式和测试阶段发现,即使花费了很多的时间和精力,也无法消除。

3.二者之间的关系

3.1:内存泄漏的堆积最终会导致内存溢出
3.2:内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误
3.3:内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,也无法被垃圾回收器回收,因为找不到他的任何信息。:
3.4:内存溢出:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。说白了就是我承受不了那么多,那我就报错。

猜你喜欢

转载自blog.csdn.net/liyuuhuvnjjv/article/details/108611391