虚拟机知识夯实之路---内存区域与内存溢出异常

1概述·

Java程序员和C语言程序员不同,由于将控制内存的权力交给了虚拟机,所以也需要了解其中的原理,以防止内存的泄露或者溢出。

2运行时的数据区域

Java虚拟机在执行java程序的过程中把内存分为不同的数据区域,这些区域有不同的作用,创建时间和销毁时间。

2.1程序计数器:

程序所执行字节码的指示器,是一块很小的内存空间。

通过改变计数器的值,可以选取下一条需要执行的字节码指令,通过这种方式来完成程序控制,它掌管着循环,跳转,异常处理等功能。

每一条线程都需要一个独立的程序计数器,线程私有

2.2虚拟机栈

生命周期与线程相同,也是线程私有。

每个方法在执行时会创建一个栈帧,这个栈帧可以储存变量表,操作数栈、动态连接等信息。方法的调用和执行完成对应着栈帧出栈和入栈。

局部变量表存放了编译期的各种基本数据类型和reference,returnaddress类型,存放单位时局部变量槽。

局部变量槽在运行时数量不变,如long、double会占用两个变量槽。不同的虚拟机变量槽大小不同。

两类异常

第一类 线程请求的栈深度过大---Stackoverflowerror

第二类 栈容量无法申请扩展---Outofmemoryerror

2.3本地方法栈

与虚拟机栈功能一致,不过机栈执行java方法,本地方法栈执行本地方法

异常和虚拟机栈一致

2.4java堆

线程共享,虚拟机启动时创建

几乎所有实例对象都在堆上分配内存。

Java堆时垃圾收集管理的内存区域,现在的收集器基于分代收集理论,但今天的收集器有所差异。

从分配内存的角度来看,将堆进行细分可以更好更快地回收和分配内存。但无论怎么细分,存储的都只能是对象实例。

Java堆现在大部分是可以扩展的。通过xmx和xms来调节。

无法扩展时有outofmemoryerror异常。

2.5方法区

与堆一样,是各个线程共享的内存区域。

用来存储已经加载的类型信息,常量,静态变量等数据。

目前本地内存的元空间取代了永生代来实现方法区。

不能满足新的内存分配时抛出out of memoryerror。

2.6运行时常量池(是方法区的一部分)

存放的是类加载后的各种字面量和符号引用,不止存放编译期的常量,运行期间也可以将新产生的常量放入常量池。

受到方法区内存的限制,申请不到内存时有outofmemoryerror异常。

2.7直接内存

不是虚拟机运行时数据区的一部分,不会受到java堆大小的限制但受到本机内存的限制。,

抛出oomerror异常。

3HotSpot虚拟机(更加细化)

(1)对象的创建

1检查类加载的情况,若没检查通过,需要进行类加载。

2虚拟机为对象分配内存

用指针碰撞或者空闲列表的方法划出可用空间

用同步处理和TLAB来保障线程安全

3虚拟机将分配的内存空间都初始化为零值,并且对对象头进行设置(对象头里的信息可以告知对象是哪个类的实例、对象的hashcode,CG分代年龄)

4构造函数的,执行class文件中的<init>方法(此时从java程序的角度来看,对象创建才刚刚开始,执行完这一步,才算被构造出来)

(2)对象的内存布局

对象在堆内存的布局有三部分:对象头、实例数据、对齐填充

1对象头:

对象头包含两类信息:

第一类:用于存储自身运行时的数据,如hashcode GC分代年龄,锁状态、线程持有的锁。它可以在极小的空间存储尽可能多的信息,被设计为动态定义的数据结构,能根据对象服用自己的存储空间

第二类:类型指针,是对象指向它的类型元素数据的指针,java虚拟机通过这个指针来确定这个对象是哪个类的实例。当对象是java数据的时候,需要记录数组的长度。

2实例数据是对象真正存储的有效信息,无论从父类继承下来的,还是在子类中定义的字段,都应该被记录下来。父类中定义的变量会出现在子类前,但有时未节省空间,可以将子类的较窄变量插入到父类变量的空隙中。

3对齐填充,没有含义,以8字节为单位,没有对齐就需要填充来补全。

(3)对象的访问定位:

创建对象是为了后续使用对象,java程序会通过java栈上的reference数据来操作堆上的具体对象。

方式一:使用句柄,java堆分为句柄池和实例池,栈中的reference 存储对象的句柄地址,句柄中有两个指针,一个指向实例池的对象实例数据,另一个指向方法区中的对象类型数据。

好处:栈中的reference存储稳定的句柄地址,当对象被移动的时候智慧改变句柄中的实例数据指针,reference本身不会被修改(垃圾收集时移动对象是非常普遍的)

方式二:直接指针。栈中的reference存储堆中的对象地址,对象中包含到对象类型的指针和实例数据,指针指向方法区中的对象类型数据。

好处:速度更快并且节省了指针定位的开销。

2.4实战:out of memory error异常

除了程序计数器以为,虚拟机内存的其他几个运行时区域都有发生out of memory error异常的可能。

2java堆溢出

oome +java heap space异常排查:Dump出当前的内存存储快照,通过分析工具对堆转储快照进行分析,确定出是内存溢出还是泄露,

如果是内存泄露,通过工具进一步找到GC root 引用链找到泄露对象的具体代码位置,如果是内存溢出,就检查堆参数设置,检查代码是否合理等。

3栈溢出

异常1:StackOverflowerror 线程请求的栈深度大于虚拟机所允许的最大深度线程的栈深度超出,新的栈帧内存无法分配,出现时先定位到具体线程再解决。

异常2:OOME,虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,抛出此异常。

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

JDK7中intern()⽅法由于字符串常量池移动⾄堆中,不需要拷⻉字符串实例到永久代,⽽是在常量池中记录其⾸次出现的实例引⽤。

5本机直接内存溢出

由直接内存溢出导致的OOME的明显特征是在Heap Dump⽂件中不会看⻅有什么明显的异常情况,如果发现内存溢出后产⽣的Dimp⽂件很⼩并且使⽤了直接内存,就需要考虑⼀下这⽅⾯的问题。

以上部分让我们明白了虚拟机里面的内存是如何划分的,哪个部分、什么样的代码和操作会导致内存溢出异常。虽然java有垃圾收集机制,但内存溢出离我们并不遥远。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值