2.1 自动内存管理机制--Java内存区域与内存溢出异常

自动内存管理机制

第二章、Java内存区域与内存溢出异常

【虚拟机中内存如何划分,以及哪部分区域、什么样代码和操作会导致内存溢出、各区域内存溢出的原因】



一、运行时数据区域

  Java虚拟机所管理的内存包括以下几个运行时数据区域【虚拟机内存模型】:

1.程序计数器:

  可以看作是当前线程所执行的字节码的行号指示器。在虚拟机中,字节码解释器工作时就是通过程序计数器的值来选择下一条需要执行的字节码指令。Java虚拟机中多线程是通过线程轮流切换并分配处理机执行时间的方式实现的,在任何一个确定的时刻,一个处理器(内核)只能处理一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每个线程都必须有一个独立的程序计数器,各线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

  如果线程执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,这个计数器的值为

  程序计数器这个内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError(内存溢出)情况的区域。

2.虚拟机栈

  Java虚拟机栈也是线程私有的,其生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成对应着一个栈桢在虚拟机中入栈到出栈的过程。

  有人会把虚拟机内存分为堆内存和栈内存,这种比较粗糙。其中的栈内存讲的就是虚拟机栈或者说是虚拟机栈中的局部变量表。局部变量表存放了编译器的各种基本数据类型、对象引用、returnAddress类型(指向字节码指令地的址)。其中64位的long和double类型数据占用两个局部变量空间(Slot),其余类型只占用一个。

  Java虚拟机规范中,①如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;②如果虚拟机栈可以动态拓展,如果拓展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3.本地方法栈

  本地方法站和虚拟机栈作用相似,虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法站则为虚拟机使用到的Native方法服务。和虚拟机栈一样,本地方法站也会抛出StackOverflowError和OutOfMemoryError异常。

4.Java堆

  Java堆是Java虚拟机管理的内存中最大的一块,是被所有线程共享的一块内存区域。在虚拟机启动时创建,唯一目的就是存放对象实例Java堆是垃圾收集器管理的主要区域,又称为GC堆,可以细分为新生代和老年代,可再细致划分为Eden空间、From Survivor空间、To Servivor空间。

  Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可以完成对象实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

5.方法区

  方法区也是线程共享的一块内存区域,用于存储已被虚拟机加载类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范将方法去描述为堆的一个逻辑部分,并且对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可拓展外,还可以选择不实现垃圾收集。

  当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常

6.运行时常量池

  运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

  Java虚拟机对Class文件每一部分(自然也包括常量池)的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

  运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

  既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

7.直接内存

  直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现,所以我们放到这里一起讲解。在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。



二、HotSpot虚拟机对象

【内存中的数据如何创建、如何布局以及如何访问】

1.Java对象的创建

  第一步:【类加载检查】虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析、初始化过,如果没有,那必须先执行相应的类加载过程【第七章】。

  第二步:【内存分配】对象所需内存的大小在类加载完成后便可以完全确定。

    内存分配方法:①指针碰撞:Java堆中内存是绝对规整的,所有用过的内存放在一边,没用过的内存放在另一边,分配内存仅仅是把指针向空闲空间那边移动一段与对象内存空间大小相等的距离。

           ②空闲列表:Java堆中内存不是规整的,虚拟机维护一个列表,记录内存使用情况。在分配的时候从列表找到一块足够大的空间划分给对象实例。

    解决并发创建对象问题:①对分配内存空间操作进行同步处理——CAS配上失败重试保证更新操作的原子性。②将内存分配动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存(本地线程分配缓冲)。

  第三步:【内存空间初始化】虚拟机需要将分配到的内存空间初始化为零值。这一步保证了对象的实例字段可以不赋值就可以直接使用。

  第四步:【必要设置】根据对象的对象头中的信息,虚拟机对对象进行必要的设置,例如该对象是哪个类的实例、如何找到类的元数据(数据的描述)信息、对象的哈希码、GC分代年龄等信息。

  此时,对于虚拟机来说Java对象的创建已经完成了,但是从Java程序来说,对象的创建才刚刚开始——init方法还未执行,所有字段还为0。把对象按照程序员的意愿进行初始化,这样一个对象才算完全产生出来。

2. 对象的内存布局

  对象在内存中存储的布局分为三个部分:对象头、实例数据、对齐填充

  第一部分【对象头】:对象头包括两部分信息:①用于存储对象自身的运行时数据,例如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。②另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象是那个类的实例。

  第二部分【实例数据】:实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的还是在子类中定义的,都需要记录下来,存储顺序收到虚拟机分配策略参数影响。

  第三部分【对齐填充】:对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的1倍或者2倍,当对象实例数据部分没有对齐时,通过对齐填充来补全。

3. 对象的访问定位

  上面讲了对象的创建和内存分配,这节将怎么使用对象,我们的Java程序需要通过虚拟机栈上的reference数据来操作堆上的具体对象。目前有两种访问方式:使用句柄和直接指针

  第一张【使用句柄】:Java堆中会划分一块内存作为句柄池,类似一个中间表,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据类型数据各自的具体地址信息。

  第二种【直接指针】:Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference存储的就是对象地址。使用直接指针最大的好处就是速度快,节省了一次指针定位的时间开销。



三、内存溢出异常OutOfMemoryError(OOM)

  内存泄漏(Memory Leak):指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎没有太大影响,但是内存泄漏堆积的后果就是内存泄露。

  内存溢出(Memory Overflow):指程序申请内存时没有足够的内存供申请者使用。或者说,给了一块int类型的数据空间,但是要存储long类型的数据=》内存不够用。

1.Java堆溢出

  堆的最小值 :-Xms;堆的最大值:-Xmx;如果将堆的最小值和堆的最大值设置为一样的,可以防止堆自动扩展。

2.虚拟机栈和本地方法栈溢出

  栈容量设置:-Xss;

  如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;

  如果虚拟机在拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常;

3.方法区和运行时常量池溢出

  方法去存放Class相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

  通过-XX:PermSize和-XX:MaxPermSize限制方法区大小。

4.本机直接内存溢出

  DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。如果发现OOM之后Dump文件很小,并且程序中直接或间接使用了NIO,那就可以考虑一下检查一下是不是直接内存溢出。

  【直接内存VS堆内存】

  ①直接内存申请空间耗费更高的性能,当频繁申请到一定量时更为明显。

  ②直接内存读写的性能优于普通堆内存,在多次读写的情况下差异明显。

原文地址:https://www.cnblogs.com/qmillet/p/12037259.html

时间: 2024-10-08 05:20:44

2.1 自动内存管理机制--Java内存区域与内存溢出异常的相关文章

JVM自动内存管理机制——Java内存区域(下)

一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a)下面是一些简单的使用参数 其中最后一个是一个运行时参数设置的简单实例.一般-XX是系统级别的配置(日志信息,或者是配置使用什么样的垃圾回收器等等),后面跟上+表示启用.不是-XX基本上是对于应用层面的配置信息 下面是一个简单的实例:表示设置初始堆大小为5M,最大堆大小为20M,并将虚拟机的参数设置打印出来,后

JVM自动内存管理机制——Java内存区域

一.JVM运行时数据区域概述 Java相比较于C/C++的一个特点就是,在虚拟机自动内存管理机制的帮助下,我们不需要为每一个操作都写像C/C++一样的delete/free代码,所以也不容易出现内存泄漏和内存溢出的问题.显然,这里的不容易只是相对而言的,如果我们想要降低这种代码隐患的发生,就需要对Java虚拟机怎样使用内存有了解,这样的话就算产生错误,排查起来也会相对容易.下面我们来说一说JVM运行时数据区域 1.程序计数器(PC寄存器): 被看作是当前线程所执行的字节码的行号指示器,字节码解析

Java虚拟机内存管理机制

自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则是依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范 第2版>规定,运行时数据区包括: 1.程序计数器 一块较小的内存空间,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它.当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令.每条

OC 内存管理机制总结

OC 内存管理机制总结 一:OC内存管理机制目前分为两块,其一自动内存管理机制,其二手动内存管理机制: 1.首先我们从自动内存管理机制讲起: 1)什么是自动内存管理机制,自动内存管理机制就是程序中所创造的成员变量交由系统统一处理,不需要外部人员干预,有点像java中gc(垃圾回收机制). 2)之前是没有自动内存管理机制的,后期苹果想拓展自己的开发市场,吸引其他平台开发者入住ios开发阵营,其中收到内存管理是很发杂的一块,对于转入IOS开发者不利,因此苹果推出了自动内存管理机制. 2.接下来我们将

PHP内存管理机制与垃圾回收机制

PHP内存管理机制 1 var_dump(memory_get_usage()); //获取内存 2 $a = "laruence"; //定义一个变量 3 var_dump(memory_get_usage()); //定义变量之后获取内存 4 unset($a); //删除该变量 5 var_dump(memory_get_usage()); //删除变量后获取内存 6 从上面可以看出php的内存管理机制是:预先给出一块空间,用来存储变量,当空间不够时,再申请一块新的空间. 1.存

Spark内存管理机制

Spark内存管理机制 Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优. 在执行 Spark 的应用程序时,Spark 集群会启动 Driver 和 Executor 两种 JVM 进程,前者为主控进程,负责创建 Spark 上下文,提交 Spark 作业(Job),并将作业转化为计算任务(Task),在各个 Executor 进程间协调任务的调度,后者负责在

【python】python值传递问题和内存管理机制

1)值传递问题 python中到底是"值传递"还是"引用传递",主要取决于对象是否是可变的.     1)函数传参:    ·基本数据类型:int.float.str:元祖属于不可变对象:传递的是"原值"的拷贝    ·列表.字典.类.类实例属于可变对象:传递的是"值引用"          2)变量复制    ·python中的变量复制(a=b)也是同样的道理,不可变对象被真正复制:而可变对象只是传递了一个     &quo

java内存管理机制

JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间.释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2. 

【Java深入研究】3、JVM内存管理机制

转自:http://blog.csdn.net/lengyuhong/article/details/5953544 近期看了看Java内存泄露的一些案例,跟原来的几个哥们讨论了一下,深入研究发现JVM里面还是有不少以前不知道的细节,这里稍微剖析一下.先看一看JVM的内部结构-- 如图所示,JVM主要包括两个子系统和两个组件.两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统:两个组件分别是Runtime data area (运行时数据区域)组