JVM内存管理

JVM 在执行 Java 程序的过程中会将其管理的内存划分为若干个不同的数据区域如下图所示,这些区域各自有不同的不同的用途、创建和销毁时间,有的区域随着虚拟机进程启动而存在,而有的则依赖用户线程的启动和结束而建立和销毁;有些区域是属于线程私有,而另外一些区域则是是线程共享。本篇将将根据各内存区域是属于线程私有还是线程共享将其分为两大类分别进行说明。

在这里插入图片描述

线程私有的内存区域

程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间(可能位于cpu的寄存器,有待确认),可以看做是当前字节码指令执行的行号指示器,记录了当前正在执行的虚拟机字节码指令地址。每个线程都有各自独立的程序计数器,注意如果正在执行的是 Native方法,则程序计数器为空(Undifined),并且 JVM 规范中并没有对程序计数器定义 OutOfMemoryError 异常。

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

虚拟机栈帧中,局部变量表是比较为人所熟知的,也就是平常所说的“栈”,局部变量表所需的内存空间在编译期间分配完成,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

虚拟机栈有两种异常情况:

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度,特别是方法的递归调用时(有空程序模拟一下)
OutOfMemoryError:虚拟机栈无法满足线程所申请的空间需求,即使经过动态扩展仍然无法满足时抛出(有空程序模拟一下)
本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈相似,不过服务于本地方法,有些虚拟机将这两个区域合二为一。本地方法栈中抛出异常的情况与虚拟机栈相同。

线程共享的内存区域

堆(Heap)
通常来说,堆是Java虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,堆的作用就是存储对象实例。
堆也是垃圾收集器所管理的主要区域,因此很多时候也被称作GC堆。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,因此堆还可以被细分为:新生代和老年代。再继续细分可以分为:Eden空间、From Survivor空间、To Survivor空间等,从内存分配的角度来看,线程共享的堆中还可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
堆可以是物理上不连续的空间,只要逻辑上是连续的即可,-Xmx和-Xms参数可以控制堆的最大和最小值。
堆的空间大小不满足时将抛出OutOfMemoryError异常。

方法区(Method Area)
用于存储已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆)。

方法区同样会抛出OutOfMemoryError异常。
在方法区中有一部分区域用来存储编译期产生的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。这里需要说明一点,常量并不是只能在编译期产生,运行期间也会产生新的常量并被发在常量池中,如 String 类的 intern() 方法。

直接内存

注意直接内存不属于虚拟机运行时数据区的一部分,也不是 JVM 规范中定义的内存区域,其主要用于 JDK1.4 引入的基于通道(Channel)和缓冲区(Buffer)的 NIO 类,可以避免在 Native 堆和 Java 堆之间来回复制数据从而提高性能。该部分内存分配不受 Java 堆内存大小的限制,但是肯定也受限于机器硬件内存的限制。

内存泄漏

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收

内存溢出

1、java.lang.OutOfMemoryError: PermGen space (持久带溢出)

我们知道jvm通过持久带实现了java虚拟机规范中的方法区,而运行时常量池就是保存在方法区中的,因此发生这种溢出可能是运行时常量池溢出,或是由于程序中使用了大量的jar或class,使得方法区中保存的class对象没有被及时回收或者class信息占用的内存超过了配置的大小。

2、java.lang.OutOfMemoryError: Java heap space (堆溢出)

发生这种溢出的原因一般是创建的对象太多,在进行垃圾回收之前对象数量达到了最大堆的容量限制。

解决这个区域异常的方法一般是通过内存映像分析工具对Dump出来的堆转储快照进行分析,看到底是内存溢出还是内存泄漏。如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,定位出泄漏代码的位置,修改程序或算法;如果不存在泄漏,就是说内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数-Xmx(最大堆大小)和-Xms(初始堆大小),与机器物理内存对比看是否可以调大。

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

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值