1,运行时数据区域
根据JAVA虚拟机规范的规定:JAVA虚拟机所管理的内存将会包括以下几个运行时数据区域

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器,通过改变计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能。每条线程都需要一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存,也是唯一不会出现OutOfMemoryError情况的区域。
JAVA虚拟机栈(Java Virtual Machine Stacks)也是线程私有,它的生命周期与线程相同,用来描述JAVA方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等。每一个方法从被调用到执行完成的过程,也就一个栈帧在虚拟机栈从入栈到出栈的过程。
在JAVA虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机可以动态扩展,当扩展到无法申请到足够的内存时,会抛出OutOfMemoryError异常。
本地方法栈(Native Method Stacks)与上述的虚拟机栈非常类似,只是虚拟机栈为执行JAVA方法服务,而本地方法栈为虚拟机使用到的Native方法服务。
JAVA堆(Java Heap)是Java虚拟机所管理内存中最大的一块,被所有线程共享,在虚拟机启动时创建,此内存区域的唯一目的就是为了存放对象实例。JAVA堆是垃圾回收器管理的主要区域。如果堆中没有足够的内存完成实例分配,并且堆无法扩展时,将会抛出OutOfMemoryError异常。
方法区(Method Area)方法区也被称为“持久代”,此内存区域与堆一样,也是线程共享的。它用于存储已被虚拟机加载的类(java.lang.Class)信息、常量、静态变量、即时编译器编译后的代码等数据。垃圾回收行为在这个区域是比较少见的,并且可以选择不回收。
当此区域无法满足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池。当常量池无法再分配到内存时,也会抛出OutOfMemoryError异常。
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是JAVA虚拟机规范中定义的内存区域,但是这部分内存也频繁被使用,并且也可能抛出OutOfMemoryError异常。如NIO可以使用Native函数库直接分配堆外内存。
2,对象访问
介绍完JAVA运行时数据区域后,再看看JAVA对象访问是如何进行的。
即使是最简单的访问,也会涉及JAVA栈、JAVA堆、方法区三个最重要的内存区域,如下代码
- Object obj = new Object();
假设这段代码出现在方法体中,那"Object obj"这部分的语义将会反映到JAVA栈的局部变量表中,作为一个reference类型的数据出现,因此就存在虚拟机栈中。 而"new Object();"这部分的语义将会反映到JAVA堆中,形成一个存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存。另外,在JAVA堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
由于reference类型在JAVA虚拟机规范中只规定了一个指向对象的引用,并没有规定用哪种方式去实现,因此不同的虚拟机实现的方式会有所不同,主流的访问方式有两种:使用句柄与使用指针:
句柄:JAVA堆中会划分出一块内存来作为字柄池,reference存放的是对象的句柄地址,而句柄中包含了对象实例和类型数据各自的具体地址信息: 如下图所示 :

指针:如果使用指针访问方式,JAVA堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址

这两种方式各有优势,使用句柄访问最大的好处就是reference存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用指针的好处就是速度快,节省一次指针定位的时间开销。Sun HotSpot虚拟机使用第二种方式进行对象访问。
3,实战:OutOfMemoryError异常
在JAVA虚拟机规范描述中:除了程序计数器外,其它几个内存区域都有发生OutOfMemoryError异常的可能,本节通过若干实例来验证异常发生的场景。
注意:每个示例代码的开头都会注明虚拟机启动参数的设置,具体设置方法如下图:

3.1:JAVA堆溢出:
- package com.chapter1;
-
- import java.util.ArrayList;
- import java.util.List;
-
-
-
-
- public class HeapOOm {
- static class OOmObject{
- }
- public static void main(String[] args) {
- List<OOmObject> oomList = new ArrayList<OOmObject>();
- while(true){
- oomList.add(new OOmObject());
- }
- }
- }
运行结果:

-Xms表示堆内存的最小值,-Xmx表示堆内存的最大值。
上例通过不断生成新对象,导致内存溢出。
3.2虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
一般在单线程程序情况下无法产生OutOfMemoryError异常
- package com.chapter1;
-
-
-
-
- public class JavaVMStackSOF {
- private int stackLength = 1;
- public void stackLeak(){
- stackLength ++;
- stackLeak();
- }
- public static void main(String[] args) throws Throwable {
- JavaVMStackSOF oom = new JavaVMStackSOF();
- try {
- oom.stackLeak();
- } catch (Exception e) {
- System.out.println("stack lenght:"+oom.stackLength);
- throw e;
- }
- }
- }
运行结果:

下面这个示例,尝试使用多线程方式得到OutOfMemeoryError的结果, -Xss参数是用于设置每个线程的堆栈大小。
- package com.chapter1;
-
-
-
-
- public class JavaVMStackOOM {
- private void dontStop(){
- while (true) {
- }
- }
- public void stackLeakByThread(){
- int i = 0;
- while(true){
- System.out.println(i++);
- Thread thread = new Thread(new Runnable() {
- public void run() {
- dontStop();
- }
- });
- thread.start();
- }
- }
- public static void main(String[] args) {
- JavaVMStackOOM oom = new JavaVMStackOOM();
- oom.stackLeakByThread();
- }
- }
3.3运行时常量池溢出(也是方法区的一部分)
- package com.chapter1;
-
- import java.util.ArrayList;
- import java.util.List;
-
-
-
-
- public class RuntimeConstantPoolOOM {
- public static void main(String[] args) {
- List<String> list = new ArrayList<String>();
- int i = 0;
- while(true){
- System.out.println(i);
- list.add(String.valueOf(i++).intern());
- }
- }
- }
运行结果:

3.4方法区溢出:
方法区用于存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。对于这个区域的测试,大概思路是运行时产生大量的类去填满方法区,直到溢出,本例使用CGLib直接操作字节码,生成大量动态类
- package com.chapter1;
-
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
-
-
-
-
- public class JavaMethodAreaOOM {
- public static void main(String[] args) {
- while(true){
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(OOMObject.class);
- enhancer.setUseCache(false);
- enhancer.setCallback(new MethodInterceptor() {
- public Object intercept(Object obj, Method method, Object[] args,
- MethodProxy proxy) throws Throwable {
- return proxy.invokeSuper(obj, args);
- }
- });
- enhancer.create();
- }
- }
- static class OOMObject{
- }
- }
由于CGLib的原因,本例在我电脑上并未调试通过。
3.5 本机直接内存溢出
DirectMemory容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与JAVA堆的最大值一样,
- package com.chapter1;
-
- import java.lang.reflect.Field;
- import sun.misc.*;
-
-
-
- public class DirectMemoryOOM {
- private static final int _1MB = 1024*1024;
- public static void main(String[] args) {
- Field unsafeField = Unsafe.class.getDeclaredFields()[0];
- }
- }

分享到:
相关推荐
Java内存区域与内存溢出异常.pdf
Java内存区域是Java虚拟机(JVM)管理内存的核心组成部分,它们主要分为以下几个部分: 1. **程序计数器(Program...开发者应当关注内存分配、垃圾收集策略以及对象访问方式,以提高程序效率并避免内存溢出异常。
这是自己读《深入理解Java虚拟机》时候用XMind建立的思维导图,目的是为了能够帮助自己整理、梳理相关的知识以及方便自己日后的回顾,帮助自己建立起关于JVM的知识体系,里边也有一些对相关内容的补充,通过备注的...
JAVA内存溢出问题总结 JAVA 内存溢出问题是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用的内存大于虚拟机能提供的最大内存。内存溢出问题可以从容器和程序类两个方面进行排查,容器问题...
Java内存区域与内存溢出异常详解 Java内存区域可以分为程序计数器、虚拟机栈、本地方法栈、堆、方法区五个部分。每个部分都有其特定的作用和生命周期。 1. 程序计数器(Program Counter Register):是当前线程所...
Java内存机制是Java虚拟机(JVM)的关键组成部分,它管理着程序运行时的数据存储。在Java中,内存主要分为以下几个区域: ...正确理解和运用Java内存机制以及异常处理机制对于开发健壮、高效的Java应用程序至关重要。
Java内存溢出问题,全称为Java OutOfMemoryError,是Java开发者经常遇到的运行时异常。内存溢出通常发生在程序运行过程中,系统无法为运行的应用程序分配足够的内存资源,导致程序无法正常执行。理解并解决Java内存...
Java内存溢出问题通常指的是程序在试图分配内存时,无法找到足够的连续内存空间而抛出的异常。在Java中,内存分为几个区域:堆(Heap)、栈(Stack)、方法区(Method Area,Java 8后被元空间取代)以及程序计数器和...
Java堆是Java虚拟机中最大的一块内存区域,用于存储对象实例。当程序创建新的对象并分配给堆时,如果堆空间不足,就会抛出`OutOfMemoryError: Java heap space`异常。Java应用程序可以通过设置JVM的启动参数来控制堆...
Java内存溢出问题通常指的是Java应用程序在运行过程中由于内存分配不当或使用过度导致JVM无法分配更多的内存,从而抛出`java.lang.OutOfMemoryError`异常。本文将深入探讨Java内存溢出的不同类型及其解决方案。 1. ...
在Java虚拟机(JVM)中,不同的内存区域负责不同的功能,并且各自可能会出现特定类型的内存溢出异常。通过本实验,旨在深入理解JVM内存管理机制以及各种内存区域的特点,并通过具体的编程实践来触发并分析这些异常,...
在 JVM 中,如果 98%的时间是用于 GC 且可用的 Heap size 不足 2%的时候将抛出内存溢出异常信息。Heap Size 最大不要超过可用物理内存的 80%,一般的要将 Xmx 和 Xms 设置相同避免每次 GC 后都要调整虚拟机堆的...
#### 一、Java内存区域与内存溢出异常 Java虚拟机(JVM)在运行过程中会管理多种不同的内存区域,这些区域各自承担着特定的任务,并且每种区域都有可能发生内存溢出异常。 ##### 1.1 程序计数器(Program Counter ...
10. **日志和异常处理**:当发生内存溢出时,JVM会生成堆转储文件(hprof),通过分析这些文件可以找出问题原因。同时,良好的异常处理和日志记录可以帮助追踪问题。 11. **监控与调优**:使用JMX(Java Management...
Java编程常见内存溢出异常与代码示例 在Java编程中,内存溢出异常是非常常见的错误之一,了解内存溢出异常的原因和解决方法是非常重要的。本文将详细介绍Java编程中常见的内存溢出异常和代码示例,帮助读者更好地...
不同的内存区域可能出现不同的内存溢出异常,如`OutOfMemoryError`。 1. `java.lang.OutOfMemoryError`: 这是最常见的内存溢出异常,可能由于以下几个原因: - JVM内存设置过小:如果分配给JVM的内存不足,当程序...
#### 二、Java内存模型与内存区域 Java虚拟机(JVM)管理着多种不同类型的内存区域,包括堆内存(Heap Memory)、方法区(Method Area)、永久代(Permanent Generation Space)等。不同的内存区域有着不同的作用和特点: ...
每一个Java应用程序启动时都会创建一个独立的Java虚拟机(JVM)实例,每个JVM实例都会有自己的Java堆内存区域。 - **内存分配与回收**:当我们在代码中创建一个新的对象时,JVM会在堆内存中为其分配一块内存。当...
Java内存管理和内存问题,特别是内存泄露与内存溢出,是Java开发者必须面对的重要主题。本文主要探讨了Java内存溢出的检查方法和工具,以及内存泄露的基本原理。 首先,当Java应用程序抛出`java.lang....
Java内存管理是Java虚拟机(JVM)的关键组成部分,它主要负责对象的创建、分配、...总的来说,理解Java内存管理与内存溢出异常对于优化应用性能、防止系统崩溃至关重要。开发者应关注内存使用,确保程序高效且健壮。