JVM(四)jvm内存区域与内存异常

本文深入解析JVM内存区域,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区及其运行时常量池。探讨了各区域的作用及它们如何影响Java程序的执行。同时,还讨论了内存溢出的原因及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM运行时内存区域

jvm在运行java程序时,会把它管理的内存划分为五个模块

程序计数器

程序计数器占JVM内存很小的一部分,主要记录jvm执行当前线程字节码文件的行号指示器,字节码解释器会根据计数器的值执行对应行号的字节码。在多线程的时候,JVM是采用线程轮流切换并分配处理器时间的方式来实现的,那么为了切换后线程能找到正确的执行位置,所以每个线程都需要一个程序计数器。所以程序计数器是每个线程私有的。

Java虚拟机栈

java虚拟机栈线程私有,描述的是JAVA方法执行的内存模型,所以他与线程的生命周期相同。那么在每个线程执行调用某一个方法的时候都会创建一个栈帧,同来存储局部变量表等等,线程每调用一个方法,从执行到结束,对应着栈帧从Java虚拟机栈入栈到出栈的过程。

局部变量表存放编译器可知的基本数据类型和对象的引用类型(地址而不是对象本身),所以局部变量表的内存在编译时分配内存

long和double类型的数据占用两个局部变量表,其他占一个。

 本地方法栈

与Java虚拟机栈不同的地方在于,它是为JVM中的native方法服务的,有的虚拟机会把这两个栈合二为一。

Java堆

堆是jvm管理的最大的内存的区域,是所有线程共享的区域,在虚拟机启动时创建。用来存放对象实例。堆是垃圾回收器管理的主要区域,所以又称为gc堆。

为了防止多线程情况下,给对象分配内存时,出现问题,线程共享的java堆可能划分多个线程私有的分配缓存区。

 方法区

所有线程共享,存储被虚拟机加载的类信息,常量,静态变量还有即时编译后的代码等数据

运行时常量池

它属于方法区的一部分,用于存放编译期间生成的各种字面量和符号引用。之所以叫运行时常量,就是它具备动态性,运行期间也可以将新的常量放入池中

直接内存

不属于jvm运行时数据区一部分,在jdk1.4之后引入一种基于通道与缓冲区的I/O方式。可以使用native的方法分配堆外内存,然后通过DirectByteBuffer对象作为这块内存的引用进行操作。


OutOfMemoryError异常(OOM)

上述所有的内存区域都会发生OOM异常,那么什么样的代码或者操作会导致不同区域的OOM异常呢,以及我么该怎么处理?

java堆溢出

堆是存放对象实例的,那么当对象数据达到最大堆的容量,且没有回收,就会内存溢出异常。

为了实验,设置eclipse的堆内存大小,-Xms堆内存的最小值,-Xmx堆内存的最大值,相等时说明不支持自动扩展

-XX:+HeapDumpOnOutOfMemoryError 让JVM出现内存溢出异常时Dump出详细信息便于分析

package xidian.lili.OOMtest;

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

/**
 * -verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
 * 
 */
public class HeapOOM {
	static class OOMObject{
		
	}
	public static void main(String[] args) {
		List<OOMObject> lists=new ArrayList<OOMObject>();
		while(true){
			lists.add(new OOMObject());
		}

	}

}

 结果:

 

那么堆内存异常该怎么解决呢?

(1)首先分析是内存溢出还是泄露

(2)内存泄露的话用工具找到泄露对象到GCroot的引用链,定位泄露代码的位置

(3)溢出的话,就检查-Xms,-Xmx参数设置,看可不可以增大Xmx,然后从代码上检查是否有些对象的生命周期过长,持有状态时间太长

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

栈是线程私有的,每个方法有自己的栈帧。

-Xoss设置本地方法栈的大小

-Xss设置虚拟机栈的大小

在jvm栈中有两种异常

StackOverflowError:当一个线程申请的请求的栈深度大于JVM所允许的最大栈深度

OutOfMemoryError:当运行时动态扩展栈失败,即没有足够的空间

但是以下两个实验均是StackOverflowError,单线程操作

(1)用-Xss设置一个很小的栈内存 -Xss128k,抛出StackOverflowError异常

(2)定义大量本地变量,增大该方法的栈帧的本地变量表的长度,(这里以为会是动态扩展栈容量)结果抛出StackOverflowError异常

所以结论:在单线程的情况下,不论是栈帧太大还是虚拟机栈内存太小,内存无法分配时,都会抛出StackOverflowError异常

那么在多线程的情况下,产生OutOfMemoryError,也和虚拟机栈内存大小没有多大关系

原因:在机器中分配给一个进程的内存大小是固定的,程序计数器及其他内存消耗忽略不计

那么-Xss=进程的固定大小减去-Xmx-再减去-XX:MaxPermSize

可以抽象的理解为-Xss=栈帧的大小*线程数,-Xss一定,线程数越多,能分配的栈帧就会越小,就需要动态扩展栈,但是空间也不够了,就会抛出OutOfMemoryError异常

那么虚拟机栈异常该怎么解决呢?

(1)减少-Xmx

(2)减少进程数

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

-XX:  PermSize   -XX:MaxPermSize限制方法区的大小

主要以String.intern()作为测试方法,这个方法是一个Native方法,在jdk1.7对该方法进行改进

jdk1.7之前:

如果字符串常量池中已经包含一个等于String对象的字符串,则返回常量池中这个字符串的引用,若果字符创常量池中没有,则复制这个字符串常量到常量池,并返回在常量池的引用

jdk1.7:

如果字符串常量池中已经包含一个等于String对象的字符串,则返回常量池中这个字符串的引用,若果字符创常量池中没有,并返回该字符串对象在堆中的引用

做了如下实验,我的jdk是1.7,如果是1.7以下就会输出false

 方法区的溢出也是很常见的溢出,我们知道方法区存放的是Class相关信息,类名,修饰符,常量池,字段描述,方法描述等,但是一个类并不容易被垃圾回收器回收

本机直接内存溢出

可以用-XX MaxDirectMemorySize来指定直接内存的大小,如果不指定,与-Xmx一样大小。

当直接内存发生OOM异常,它不会直接向操作系统申请分配内存,而是通过计算知道无法分配后就手动抛出异常,所以在Heap Dump中不会看出明显的异常,所以如果OOM之后发现dump很小,程序又使用的NIO,就可以检查是不是直接内存溢出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值