一、Java内存区域与内存溢出异常
- 程序计数器
- 较小的内存空间,记录着当前线程执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能。
- 唯一一个没有规定那个OutOfMemoryError的内存区域
- Java虚拟机栈
- 线程私有
- 每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息
- 两种异常:如果线程请求的栈深度超过Java虚拟机允许的深度将抛出StackOverFlow;对于可以动态增长的Java虚拟机,如果申请时没有足够内存将抛出OutOfMemoryError异常
- 本地方法栈
- 与虚拟机栈的区别是本地方法栈为执行Javanative方法服务
- 与虚拟机栈一样,本地方法栈也会抛出同样的两个异常
- 堆
- Java Heap是Java虚拟机管理的最大内存区域,所有对象实例和数组都在堆上进行分配
- Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”
- 从内存回收角度看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为“新生代”和“老年代”
- 如果从内存分配角度看,Java Heap还可划分由多个线程私有的分配缓冲区。不过,无论如何,都与存放内容无关,因为Java Heap上存放的都是对象实例,进一步划分的目的只是为了GC更好的回收或者更快的分配内存
- Java Heap可以是物理上不连续的内存空间,主要逻辑上是连续的即可,就像磁盘一样。在实现时可以是固定的也可以是可扩展的,通过-Xmx和-Xms控制
- 如果没有内存可以再分配将抛出OutOfMemoryError异常
- 方法区
- 线程共享的内存区域
- 存储类信息、常量、静态变量、即时编译器编译的代码等
- 平常所说的永生代其实根本不存在,只不过常用的HotSpot设计人员只把GC扩展至方法区,或者说用永生代实现的方法区,很多其他的jvm不存在永生代这个概念
- 并非数据进入了方法区就永久了,而是GC对这块的回收较少,收集目标主要是方法区的常量池和对类型的卸载,一般来说对这块数据的回收很难达到令人满意的效果
- 当方法区无法满足内存分配需求时将抛出OutOfMemoryError异
- 运行时常量池
- 存放编译器生成的各种字面常量、符号引用、直接引用
- 可以动态内存扩增
- 并不是只有编译期生成的数据才放到常量池,运行期间也可以把新的常量放入池中,典型的例如String.intern()方法
- 也会抛出OutOfMemoryError异常
- 直接内存
- 有native函数库直接分配的对外内存,然后通过在堆中保存的DirectByteBuffer对象作为这块而内存的引用。这样能在一些场景中显著提高性能,避免了在Java堆和native堆中来回复制数据
- 例如:java的nio,提供了通道(channel)和缓冲区(buffer)的i/o方式
- 虚拟机管理员在分配内存时经常只是分配堆内存忽略了直接内存,导致对内存和直接内存总和大于实际内存或超出了服务器cpu的寻址空间而抛出OutOfMemoryError异常
- 对象访问
- 例如简单的代码Object object = new Object();
- 直接指针访问对象
- object存在栈中
- new Object()保存在堆中
- 而该对象类型例如类信息等对象类型数据保存在方法区中
- 句柄访问方式
- 在堆中存储new Object()和句柄池,句柄池中保存了对象的指针
- 栈中保存句柄池地址
- 方法区中保存对象类型数据
- 两种方式对比
- 第一种访问时节省了一次寻址,访问更快;第二种在垃圾收集对堆中对象地址更改时,只影响句柄池中的映射关系,不会影响栈中引用关系(GC回收时,对象地址改变很正常)
- 现在使用的SUN HotSpot采用第一种方式实现
- 直接指针访问对象
- 例如简单的代码Object object = new Object();
- ****windows平台中的虚拟机中,Java的线程是映射到操作系统的内核线程上的****