JVM内存分配过程与原理详解(雷惊风)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

JVM内存分配过程与原理解析

之前对java虚拟机对于内存的分配与管理不是很了解,这段时间工作不是很忙,想借此机会深入的了解一下,在网上看了很多文章,对其详情也有了一定的认识,但是只是看看肯定是不行的,为了加深印象同时使自己能够理解的更深刻,我决定写这篇文章,同时希望对大家也有一定的帮助。文章里引用了其他前辈的一些资源,在这里表示感谢,那么我们就先从内存区域说起吧!

一.内存分区。

首先Java程序运行Java代码是发生在JVM上的,JMV相当于是java程序与操作系统的桥梁,JVM具有平台无关特性,所以java程序便可以在不同的操作系统上运行。Java的内存分配就是发生在JVM上的。对于java的内存回收我们并不用像其他有些语言一样手动回收,虚拟机就帮我们解决了,也正因为如此,如果我们写代码的时候不注意,很容易出现内存泄漏或者内存溢出(OOM),一旦出现问题,排查也不是很容易,所以只有了解了java的内存机制,才能更好的处理代码,优化代码。下边我们看一下java内存的几个部分,如下图:

由上图可知java内存共由java堆区(Heap)、java栈区(Stack)、方法区(Method Area)、本地方法栈(Native Method Stack)、程序计数器五部分组成,下面我们一一简单的讲解一下每一个区间的不同作用。

1.java堆区

首先要讲的就是我们的java堆,也就是人们常说的堆栈堆栈里边的堆,通过上图可知堆区是JVM中所有线程共享的内存区域,当运行一个应用程序的时候就会初始化一个相应的堆区,堆区可以动态扩展,如果我们需要的内存不够了,并且内存不能扩展了,那么就会报OOM了。引用java虚拟机规范中的一段话:所有的对象实例和数据都要在堆上进行分配。比如我们通过new来创建一个对象,创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类型的不同对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。给对象分配内存就是把一块确定大小的从堆内存中划分出来,一般有两种方式:

①指针碰撞法:假设堆中内存是完整的,已分配的内存和空闲内存分别在不同的一侧,

通过一个指针作为分界点,需要分配内存时,仅仅需要把指针往空闲的一端移动与对象大小相等的距离。②空闲列表法:事实上,Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当需要分配内存时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录。然而

创建是一个非常频繁的行为,进行堆内存分配时还需要考虑多线程并发问题,可能出现正在给对象A分配内存,指针或记录还未更新,对象B又同时分配到原来的内存,解决这个问题有两种方案:1、采用CAS保证数据更新操作的原子性;2、把内存分配按照线程进行划分,在不同的空间中进行,每个线程在Java堆中预先分配一个内存块,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。在堆中分配的内存,是由java虚拟机管理回收的。在堆中产生一个对象或数组,在栈中我们可以写多个不同的引用变量指向他,那么我们多个引用相当于指向了堆内存中的同一个内存地址,那么我们用“==”作比较时,就会返回true。我们的引用变量在栈区中分配,当程序执行完我们的某个引用变量时,我们的引用变量便会自动释放,而他指向的堆区的对象不会被回收或者说不会被马上回收,而是在后续的某个不确定的时刻GC去检查该对象还有没有被引用,如果没有被引用才会回收所占内存区域。这也是java比较占内存的一个原因。

2.Java栈区

由上图可知,栈区是线程私有的,也就是说,每一个线程都会对应一个自己的栈区,生命周期也线程相同,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),也就是指针,对象都存放在堆区中,每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。虚拟机栈描述的是Java方法执行的内存模型:每个线程在执行一个方法时会创建一个对应的栈帧(Stack Frame),栈帧负责存储局部变量变量表、操作数栈、动态链接和方法返回地址等信息。每个方法的调用过程,相当于栈帧在Java 栈的入栈和出栈过程。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。Java的数学计算是在内存栈里操作的。

3.方法区

方法区也是线程共享的区域,用于存储已经被虚拟机加载的类信息,常量,静态变量和即时编译器(JIT)编译后的代码等数据。Java虚拟机把方法区描述为堆的一个逻辑分区,不过方法区有一个别名Non-Heap(非堆),用于区别于Java堆区。方法区包含所有的class和static变量。运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等信息以外,还有一项信息是常量池用于存储编译器生成的各种字面量和符号引用,这部分信息将在类加载后存放到方法区的运行时常量池中。Java

虚拟机对类的每一部分(包括常量池)都有严格的规定,每个字节用于存储哪种数据都必须有规范上的要求,这样才能够被虚拟机认可,装载和执行。一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java虚拟机并不要求常量只能在编译期产生,也就是并非预置入Class文件常量池的内容才能进入方法区的运行时常量池中,运行期间也可将新的常量放入常量池中。常量池是方法区的一部分,所以受到内存的限制,当无法申请到足够内存时会抛出OutOfMemoryError异常。

4.本地方法栈

本地方法栈和虚拟机栈基本类似,只不过Java虚拟机栈执行的是Java代码(字节码),本地方法栈中执行的是本地方法的服务。本地方法栈中也会抛出StackOverflowError 和OutOfMemory异常。

5.程序计数器

程序计数器是线程私有的,每个线程都有独立的指令计数器,计数器记录着虚拟机正在执行的字节码指令的地址,分支、循环、跳转、异常处理和线程恢复等操作都依赖

相关文档
最新文档