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

Java虚拟机在执行java程序时会把它所管理的内存会分为若干个不一样的数据区域,不一样的区域在内存不足时会抛出不一样的异常。html

1、运行时数据区域的划分

(1)程序计数器
程序计数器(Program Counter Register)是一块比较小的内存空间,它能够看做是当前线程所执行的字节码的行号指示器;
PCR为线程私有内存,程序计数器是惟一一个在Java虚拟机规范中没有规定任何OOM状况的区域。java

(2)方法区
方法区(Method Area)与Java堆同样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
也称为永久代(Permanent Generation)但随着Java8的到来,已放弃永久代改成采用Native Memory来实现方法区的规划。
此区域回收目标主要是针对常量池的回收和对类型的卸载。
git

(3)JVM栈
虚拟机栈(Java Virtual Machine Stacks)描述的是Java方法执行的内存模型:每一个方法在在执行的同时都会建立一个栈帧(Stack Frame)用于存储局部变量表、操做数栈、动态连接、方法接口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈出栈的过程。
Java虚拟机栈也是线程私有,它的生命周期与线程相同。
Java内存区常分为堆内存(Heap)和栈内存(Stack);github

OOM状况:
线程请求的栈深度>虚拟机所运行的最大深度;
虚拟机动态扩展时没法申请到足够的内存多线程

(4)本地方法栈
与虚拟机栈做用很类似,区别是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为虚拟机用到的Native方法服务。和虚拟机栈同样可能抛出StackOverflowError和OutOfMemoryError异常。框架

(5)Java堆
Java堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。主要存放对象实例。
Java堆是垃圾收集器管理的主要区域,其可细分为新生代和老年代。若是在堆中没有内存完成实例分配,而且也没法再扩展时,会抛出OutOfMemoryError异常。工具

(6)直接内存
直接内存(Direct Memory)并非虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。
能在一些场景中显著提升性能,由于避免了在Java堆和Native堆中来回复制数据。
直接内存的分配不会受到Java堆大小的限制,但会收到本机总内存(RAM以及SWAP/分页文件)大小以及处理器寻址空间的限制。
设置Xmx等参数信息时注意不能忽略直接内存,否则会引发OOM。布局

2、对象的访问定位

Java程序须要经过栈上的reference数据来操做堆上的具体对象,
对象访问方法取决于不一样的JVM实现,目前主流访问方式有使用句柄和直接指针2种。性能

(1)句柄访问学习

Java堆中划分出一块内存做为句柄池,reference中存储对象的句柄地址,句柄中包含对象实例数据与类型数据各自的具体地址信息;

 

(2)直接指针访问

Java堆对象的布局中必须考虑如何放置访问类型数据的相关信息,reference中存储对象地址;

 

(3)两种访问方式比较
使用句柄访问最大的好处是reference中存储的是稳定的句柄地址,在对象被移动(GC时移动对象是很广泛的行为)时只会改变句柄中的实例数据指针,而reference自己不须要修改;
使用直接指针访问方式的最大好处是速度更快,它节省了一次指针定位的时间开销,因为对象访问在Java中很是频繁,所以这类开销聚沙成塔后也是一项很是可观的执行成本;
HotSpot虚拟机采用指针访问方式进行对象访问,从整个软件开发范围看,各类语言和框架使用句柄来访问的状况也很是常见。

3、几种内存溢出异常及解决

虚拟机主要的几种异常是OutOfMemoryError异常和虚拟机栈和本地方法栈溢出,以及运行时常量池溢出等。

(1)OutOfMemoryError异常

除了程序计数器外,虚拟机内存的其余几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能,

java堆用于存储对象实例,咱们只要不断的建立对象,而且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
出现这种异常,通常手段是先经过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是不是必要的,先分清是由于内存泄漏(Memory Leak)仍是内存溢出(Memory Overflow)。
若是是内存泄漏,可进一步经过工具查看泄漏对象到GC Roots的引用链。因而就能找到泄漏对象时经过怎样的路径与GC Roots相关联并致使垃圾收集器没法自动回收。
若是不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。

(2)虚拟机栈和本地方法栈溢出
若是线程请求的栈深度大于虚拟机所容许的最大深度,将抛出StackOverflowError异常。
若是虚拟机在扩展栈时没法申请到足够的内存空间,则抛出OutOfMemoryError异常
这里须要注意每一个线程分配到的栈容量越大,能够创建的线程数就越少,创建线程时候就越容易耗尽剩余内存。

按虚拟机默认参数,栈深度在大多数状况下达到1000~2000彻底没问题,对于正常方法调用(包括递归),这个深度应该彻底够用;

但若是是创建过多线程致使内存溢出,在不能减小线程数或者更换X64位虚拟机的状况下,就只能经过减小最大堆和减小栈容量来换取更多的线程

(3)方法区和运行时常量区溢出
java.lang.OutOfMemoryError: PermGen space
运行时常量池溢出(HotSpot虚拟机中的永久代),
若是要向运行时常量池中添加内容,最简单的作法就是使用String.intern()这个Native方法。

运行时常量池溢出(HotSpot虚拟机中的永久代),
若是要向运行时常量池中添加内容,最简单的作法就是使用String.intern()这个Native方法。
该方法的做用是若是池中已经包含一个等于此String的字符串,则返回表明池中这个字符串的String对象;不然,将此String对象包含的字符串添加到常量池中,而且返回此String对象的引用。
因为常量池分配在方法区内,咱们能够经过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

 

4、Java如何得到JVM内存使用状况 

要得到JVM相关的内存信息,须要使用Runtime类的totalMemory(), maxMemory() 和 freeMemory()三个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.text.DecimalFormat;
 
public class Test{
 
/**
* 显示JVM总内存,JVM最大内存和总空闲内存
*/
public void displayAvailableMemory() {
 
DecimalFormat df = new DecimalFormat(“ 0.00 ″) ;
 
//显示JVM总内存
long totalMem = Runtime.getRuntime().totalMemory();
 
System.out.println(df.format(totalMem 1000000F) + ” MB”);
 
//显示JVM尝试使用的最大内存
long maxMem = Runtime.getRuntime().maxMemory();
System.out.println(df.format(maxMem 1000000F) + ” MB”);
 
//空闲内存
long freeMem = Runtime.getRuntime().freeMemory();
System.out.println(df.format(freeMem 1000000F) + ” MB”);
 
}
 
/**
* Starts the program
* @param args the command line arguments
*/
public static void main(String[] args) {
new Main().displayAvailableMemory();
}
}

  

参考

JVM学习笔记(二)Java内存区域与内存溢出异常

Java 内存区域与内存溢出

 

转:http://www.cnblogs.com/binyue/p/3927105.html

相关文章
相关标签/搜索