`

《JVM笔记》之一:Java内存区域与内存溢出异常

    博客分类:
  • Java
阅读更多

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

按照《Java虚拟机规范(第2版)》的规定,Java虚拟机所管理的内存将包括以下几个运行时数据区域,来个图更加直观点,如下图所示:



 

解释下各个部分

 

程序计数器:

Program Counter Register是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。 每个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

Java虚拟机栈:

也是线程私有的,它的生命周期与线程相同。每个方法被执行的时候会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。

如果线程请求栈深度大于虚拟机所允许的深度,抛出StackOverflowError

如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存时会抛出OutOfMemoryError

 

本地方法栈:

Native Method Stacks与虚拟机栈所发挥的作用是非常相似的,只不过一个是执行Java方法,一个是Nataive方法,HotSpot虚拟机直接将两者合二为一了

 

Java堆:

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。Java堆是垃圾收集器管理的主要区域,很多时候称为GC堆。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError

 

方法区:

Method Area与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError。

 

运行时常量池:

Runtime Constant Pool是方法区的一部分。用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

 

直接内存:

Direct Memory并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分也是频繁使用。在Java的NIO中使用到,服务器管理员忽略直接内存后果是,各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。

 

 

 实战:OutOfMemoryError异常:

1,Java堆溢出:

Java堆用于存储对象实例,我们只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免GC清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。

VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError

XX:+HeapDumpOnOutOfMemoryError这个参数可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。

 

import java.util.ArrayList;
import java.util.List;

/**
 * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
 * @author Administrator
 *
 */
public class HeapOOM {
	static class OOMObject{
		private String name;
		public OOMObject(String name) {
			this.name = name;
		}
	}
	public static void main(String[] args) {
		List<OOMObject> list = new ArrayList<OOMObject>();
		long i = 1;
		while(true) {
			list.add(new OOMObject("IpConfig..." + i++));
		}
	}
}
 抛出的异常:

 

Dumping heap to java_pid27828.hprof ...

Heap dump file created [14123367 bytes in 0.187 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:45)

at java.lang.StringBuilder.<init>(StringBuilder.java:92)

at com.baoxian.HeapOOM.main(HeapOOM.java:22)

 

 注:出现Java堆内存溢出时,异常堆栈信息 java.lang.OutOfMemoryError 后面会紧跟着 Java heap space。

要解决这个异常,一般手段是首先通过内存映像分析工具比如Eclipse Memory Analyzer对dump出来的堆转储快照进行分析,重点是确认内存中对象是否是必要的,也就是要弄清楚到底是出现了内存泄露 Memory Leak还是内存溢出 Memory Overflow。

如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链。于是就能找到泄露对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们。掌握了泄露对象的类型信息,以及GC Roots引用链的信息,就可以比较准确的定位出泄露代码的位置了。

如果不存在泄露,那么就该修改-Xms 和 -Xms堆参数看能否加大点。

 

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

-Xoss参数设置本地方法栈大小,对于HotSpot没用。栈容量只由-Xss参数设定。

 

/**
 * VM Args: -Xss128k
 * @author Administrator
 *
 */
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 (Throwable e) {
			System.out.println("stack length: " + oom.stackLength);
			throw e;
		}

	}

}
 

 

 抛出异常:

stack length: 1007

Exception in thread "main" java.lang.StackOverflowError

at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)

at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)

at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)

at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)。。。。

 

3,运行时常量池溢出:

运行时常量池分配在方法区内,可以通过 -XX:PermSize和 -XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量。

 

import java.util.ArrayList;
import java.util.List;

/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 * @author Administrator
 *
 */
public class RuntimeConstantPoolOOM {
	public static void main(String[] args) {
		// 使用List保持着常量池引用,避免Full GC回收常量池行为
		List<String> list = new ArrayList<String>();
		// 10MB的PermSize在integer范围内足够产生OOM了
		int i = 0;
		while (true) {
			list.add(String.valueOf(i++).intern());
		}
	}
}
 异常:

 

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

at java.lang.String.intern(Native Method)

at com.baoxian.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)

运行时常量池溢出,在java.lang.OutOfMemoryError后面紧跟着是PermGen space

 

4,方法区溢出:

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述符、方法描述等。对于这个区域的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。比如动态代理会生成动态类。

使用CGLib技术直接操作字节码运行,生成大量的动态类。当前很多主流框架如Spring和Hibernate对类进行增强都会使用CGLib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载入内存。

异常:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

at java.lang.String.intern(Native Method)

同样,跟常量池一样,都是PermGen space字符串出现

方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是非常苛刻的。在经常动态生成大量Class的应用中,需要特别注意类的回收状况。这类场景除了上面提到的程序使用GCLib字节码技术外,常见的还有: 大量JSP或动态产生的JSP文件的应用(JSP第一次运行时需要编译为Java类)、基于OSGi应用等。

 

5,本机直接内存溢出:

DirectMemory容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值-Xmx指定一样。

/**
 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
 * @author Administrator
 *
 */
public class DirectMemoryOOM {
	private static final int _1MB = 1024 * 1024;
	public static void main(String[] args) {
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = (Unsafe) unsafeField.get(null);
		while(true) {
			unsafe.allocateMemory(_1MB);
		}
	}
}

 

在OutOfMemoryError后面不会有任何东西了,这就是DirectMemory内存溢出了。

 

本人博客已搬家,新地址为:http://www.pycoding.com/

  • 大小: 63.8 KB
分享到:
评论

相关推荐

    狂神说JVM探究.rar

    【狂神说JVM探究】是一份集合了多种格式的学习资料,主要涵盖了Java虚拟机(JVM)的基础知识。这份资料出自B站上的【狂神说Java】系列教程,为快速入门JVM提供了详实的笔记。以下是根据这些资源可能包含的一些关键...

    jvm视频及笔记

    3. **内存模型**:包括堆内存、栈内存、方法区(在Java 8之后变为元空间)、程序计数器、本地方法栈等,理解它们的作用有助于避免内存溢出和内存泄漏问题。 4. **垃圾收集**:JVM如何自动管理内存,理解不同垃圾...

    JVM笔记(阳哥).zip

    《JVM笔记(阳哥)》是一份深入探讨Java虚拟机(JVM)的资料,由阳哥精心整理。这份笔记涵盖了JVM的基础概念、内存管理、类加载机制、性能优化等多个方面,对于理解Java程序的运行机制以及提升开发效率具有重要的...

    Java堆栈内存分析笔记

    堆和栈是Java内存管理的两个主要区域,它们各自承担着不同的职责。本笔记将深入探讨这两个区域的工作原理以及如何进行有效的分析。 首先,我们要理解Java内存的两个主要部分:堆(Heap)和栈(Stack)。堆主要用于...

    深入JVM笔记word版

    #### 一、Java内存区域概述 Java虚拟机(JVM)作为Java程序的运行环境,负责管理和分配内存资源。为了更好地理解和掌握JVM内部的工作机制,本篇文章将重点介绍JVM中的几个关键内存区域:程序计数器、Java虚拟机栈、...

    JVM 学习笔记(Java虚拟机)

    **JVM学习笔记(Java虚拟机)** ...通过深入学习JVM,我们可以更好地理解和优化Java程序,解决内存溢出、性能瓶颈等问题,提升系统的稳定性和效率。不断探索JVM的细节,有助于成为一名优秀的Java开发者。

    JVM笔记.docx

    堆内存(Heap)是JVM中最大的一块内存区域,用于存储对象实例。堆是线程共享的,它被划分为新生代(Young Generation)和老年代(Old Generation),新生代又细分为Eden空间、From Survivor空间和To Survivor空间,...

    JVM内存结构笔记.rar

    理解这些内存区域的工作原理对于识别和解决内存泄漏、性能瓶颈以及理解垃圾收集机制至关重要。例如,新生代的对象如果频繁存活,可能会导致大量对象晋升到老年代,进而可能导致老年代过早耗尽,引发Full GC。通过...

    JVM成神之路笔记整理版

    8. **JVM内存溢出与异常**:学习如何识别和处理常见的JVM错误,如OOM(Out Of Memory)错误,以及如何通过调整JVM配置避免这些问题。 9. **JDK和JRE**:JDK包含JRE和开发工具,如编译器javac和调试器jdb。JRE则包含...

    笔记,1、虚拟机的前世今生和java内存区域1

    Java虚拟机(JVM)是Java程序的核心组成部分,它负责解析和执行字节码,同时管理内存区域。本文主要探讨虚拟机的历史、运行时数据区域以及内存区域的配置。 首先,虚拟机的历史简述,虽然这里并不需要深入,但我们...

    堆内存及JVM内存设置参数 小笔记

    堆内存是Java中用于存储对象实例的区域,它在JVM内存模型中占据核心位置。在深入理解堆内存结构及其设置参数之前,我们需要明确几个概念: 1. **堆内存结构**:在Java堆内存中,主要分为两大块:**新生代(Young ...

    JVM-1.内存结构笔记

    JVM 内存结构是 Java 虚拟机的核心组成部分之一,包括程序计数器、虚拟机栈、本地方法栈、堆和方法区五个主要部分。 1. 程序计数器 程序计数器(Program Counter Register)是线程私有的,用于记录当前线程执行的...

    笔记-JVM三期.pdf

    2. **堆(Heap)**:所有对象实例和数组都存储在这里,是JVM中最大的一块内存区域,被所有线程共享。 3. **栈(Stack)**:每个线程都有一个独立的栈,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。 4....

    学习jvm笔记.zip

    4. 内存溢出:了解各种内存溢出异常,如OOM(Out Of Memory),并针对性地解决。 六、JVM监控与诊断工具 JDK提供了一些工具帮助开发者监控和诊断JVM,如JConsole、VisualVM和JProfiler,它们可以显示JVM内存状态、...

    Java学习笔记_内存管理.rar

    这份"Java学习笔记_内存管理.pdf"很可能是详细解析了Java如何进行内存分配、垃圾回收以及内存泄漏等相关概念。下面,我们将深入探讨Java内存管理的一些核心知识点。 1. **Java内存模型**: - **堆内存(Heap)**:...

    jvm学习笔记(jvm内存模型&垃圾收集算法&类加载机制)

    Java虚拟机(JVM)是Java程序的核心组成部分,它负责执行字节码并管理内存。在JVM的学习中,理解其内存模型、垃圾收集算法以及类加载机制至关重要。 1. **JVM内存模型** - **方法区**:也称为“永久代”,存储...

    JVM和性能优化学习思维笔记.rar_java

    5. **内存溢出与性能监控**:内存溢出(OOM)是常见的性能问题,可以通过-XX:+HeapDumpOnOutOfMemoryError参数让JVM在发生OOM时生成堆转储文件进行分析。使用JVisualVM、VisualVM或JMX等工具可以实时监控JVM的运行...

    JVM思维导图,学习思维笔记

    1. 堆(Heap):所有对象实例都在堆中分配内存,是JVM中最大的一块内存区域,且被所有线程共享。堆分为新生代和老年代,新生代又细分为Eden区和两个Survivor区(From、To)。 2. 方法区(Method Area):存储类信息...

    jvm学习笔记

    这篇笔记将深入探讨JVM的工作原理、内存管理、类加载机制以及优化策略,帮助读者全面理解JVM并提升Java程序的性能。 一、JVM概述 Java虚拟机作为一个抽象的计算机,它具有指令集、硬件架构和操作系统,使得Java...

Global site tag (gtag.js) - Google Analytics