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

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

1.运行时数据区域

Java虚拟机运行时数据区

1.1程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,因此是线程私有的

1.2Java虚拟机栈

线程私有,描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数帧、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈。

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态扩展,当扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

1.3本地方法栈

与虚拟机栈相似,区别是本地方法栈为虚拟机使用到的Native方法服务,也是线程私有的

1.4Java堆

是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,此区域的唯一目的就是存放对象实例,同时也是GC的主要区域,通过-Xmx(最大值)和-Xms(最小值)来控制大小

1.5方法区

线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量等数据

1.6运行时常量池

方法区的一部分,用于存放编译期生成的各种字面量和符号的引用,这部分内容将在类加载进入方法区的运行时常量池存放,具有动态性,利用的比较多的便是String类的intern()方法

TLAB:Thread Local Allocation Buffer,本地线程分配缓存,即每个线程在Java堆中预先分配一小块内存,虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB

2.内存溢出异常

2.1Java堆溢出

Java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达路径,就会在对象数量达到最大堆的容量限制后就会产生内存溢出异常,将-Xms和-Xmx设置为一样的参数,可以避免自动扩展,通过参数-XX:+HeapDumpOnOutOfMeoryError可以让虚拟机在出现内存溢出异常时Dump出当前的堆转储快照以便事后进行分析。

重点是确认是内存泄漏还是内存溢出,如果是内存泄漏,通过工具查看GC Roots的引用链,定位到泄漏的代码位置;如果不存在泄漏,那查看虚拟机参数与物理机器内存对比,是否还可以调大,代码上检查是否有对象生命周期过长,持有转态时间过长的情况。

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

由于HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定。

在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常

如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减小栈容量来换取更多的线程。

2.3方法区和运行时常量池异常

String.intern()是一个Native方法,作用是:如果字符串常量池中已经包含了一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

调整方法区大小:-XX:PermSize -XX:MaxPermSize

2.4本机直接内存溢出

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

由于DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,那可以往这方面检查。

猜你喜欢

转载自blog.csdn.net/weixin_45636641/article/details/108515066