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

1.Java内存区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是以来用户线程的启动和结束而建立和销毁。

  • 程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,称这类内存区域为“线程私有”的内存。
  • Java虚拟机栈也是线程私有的,它的生命周期与线程相同,虚拟机描述的是Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法调用直至执行完成的过程,就对应一个栈帧在虚拟机中从入栈到出栈的过程。
  • 本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的native方法服务。(一个Native Method就是一个Java调用非Java代码的接口)
  • Java堆是Java虚拟机所管理的内存中最大的一块,它是被所有线程共享的一块内存区域,在虚拟机启动时创建,此区域唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
  • 方法区和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。另外,运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

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

 

2.对象访问

Object obj = new Object();

现假设这句代码出现在方法体中,那么Object object 这部分的语义将会反映到Java栈的本地变量中,作为一个reference类型数据出现,而new Object() 这部分的语义将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据的地址信息,这些数据存储在方法区。

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

3.解决Java堆内存的OOM异常(OutOfMemoryError)

通过内存映像分析工具(如Eclipse Memory Analyze)对dump出的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,即分析是内存泄漏还是内存溢出。

如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Root引用链,查明垃圾收集器为什么无法自动回收它们。

如果是内存溢出,且内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数(-Xmx与-Xms)与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。