您的位置:首页 > 编程语言 > Java开发

深入理解java虚拟机-Java内存区域与内存溢出异常

2019-03-23 16:26 417 查看

深入理解java虚拟机

Java内存区域与内存溢出异常

运行时数据区域

程序计数器

线程私有,内存小,是当前线程执行的字节码行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令,例如(分支、循环、跳转、异常处理、线程恢复)。

 

Java虚拟机栈

线程私有,生命周期与线程相同,每个java方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用到执行完成的过程都对应一个栈帧在虚拟机栈中从入栈到出栈过程。

 

本地方法栈

为java虚拟机使用的Native方法服务(C语言编写的)

 

Java堆

内存大,所有线程共享,在虚拟机启动时创建,唯一目的是存对象实例,大多对象实例以及数组在堆上分配内存,(还有栈上分配,标量替换)

Java堆分为新生代、老年代(Eden区,S0区,S1区)进一步划分的目的都是为了更好地回收内存,或者更快地分配内存

 

方法区

线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,回收目标主要是对于常量池的卸载和对类型的卸载。

 

运行时常量池

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

直接内存

NIO类,基于通道与缓冲区的I/O方式,使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的Buffer对象作为这块内存的直接引用进行操作,提高性能(避免在java堆和Native堆中来回复制数据)

HotSpot虚拟机对象探秘

对象的创建

普通对象的创建,不包括数组和Class对象的创建过程?

  • 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程。
  • 在类加载检查通过后,虚拟机将会为新生对象分配内存(大小在类加载完成后便可完全确定),分配内存有指针碰撞(内存规整,垃圾收集器采用的是压缩整理算法),空闲列表(内存不规整)
  • 对象在虚拟机创建频繁,需要考虑并发下线程安全问题,解决办法有:①同步分配内存操作—虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;②把内存分配的动作按照线程划分在不同的空间进行,也就是为每个线程的java堆中预分配各自的一小块内存
  • 接下来,虚拟机要对对象进行设置,比如对象是哪个类的实例,类的元数据信息,对象的哈希码,对象的GC分代年龄等信息保存在对象头中。
  • 执行init方法,把对象初始化

 

对象的内存布局

分为三块区域:对象头、实例数据、对齐填充

对象头

  • 包括两个部分,第一个部分用于存储自身运行时的数据,如hashCode,GC分代年龄,锁状态,线程持有的锁,偏向线程ID,偏向时间戳等;
  • 第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,但查找对象的元数据信息并不需要经过对象本身,后面讨论…

实例数据

是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容,无论是在父类中定义的还是在子类中定义的字段都会记录下来。

对齐填充

占位符作用,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

 

对象的访问走位

目前主流的访问对象方式:使用句柄直接指针

使用句柄好处:

java栈的本地变量表中的reference存储的是稳定的句柄地址,在对象被移动(垃圾收集过程)只会改变句柄中的实例数据指针,而reference本身不需要修改。

使用直接指针好处:

         速度更快,节省一次指针定位的时间开销(对象访问频繁,可以节省时间),对应HotSpot而言,它使用直接指针方式进行对象访问。

 

实战:OutOfMemoryError异常

Java堆溢出

产生原因

java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间可达路径来避免垃圾回收机制清除这些算法,那么在对象数量达到最大堆的容量限制后,就会产生内存溢出异常。

解决办法

  • 先通过内存映像分析工具(EMA)对Dump出来的堆快照进行分析,重点是确认是内存泄漏(没用的对象还活着,死不了)还是内存溢出(都是有用的对象);
  • 如果是内存泄漏:可通过工具查看泄漏对象到GC  Roots的引用链定位泄漏代码位置;如果是内存溢出:需要检查虚拟机的堆参数(-Xmx与-Xms)与机器内存对比看是否可以调大,再从代码层次检查是否存在某些对象生命周期过长,持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

 

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

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  • 如果虚拟机在扩展时无法申请到足够的内存空间,将抛出OutOfMemoryError 异常。

 

解决办法

  1. 使用-Xss 参数设置栈内存容量
  2. 代码层次,少使用大量本地变量

 

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

运行时常量池也是方法区的一部分,方法区用于存放Class的相关信息,比如类名、访问修饰符、常量池、字段描述、方法描述等,溢溢出可以在运行时产生大量的类区填充方法区。

 

本机直接内存溢出

由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看到明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或者间接使用了NIO,就可以考虑这方面的原因了。

(adsbygoogle = window.adsbygoogle || []).push({});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java