前言: 在学习 Java 第一课时, 老师就讲到: Java 不同于 C/C++ 的手动内存分配与回收. 原来这都得益于 JVM 的内存自动管理机制, 但是在这背后又暗藏什么玄机呢??? 本人从图书馆借来了《Java 虚拟机精讲》来一探究竟.
一. JVM 的内存模型:
如下图所示可以分为 5 个模块: 堆, 栈, 本地方法栈, PC 寄存器, 方法区.
这些内存区域用来存储程序运行时的数据.
根据线程访问的权限不同, 其中线程共享的内存区域: 堆, 方法区, 运行时常量池; 线程私有的内存区域: 栈, 本地方法栈, PC 寄存器.
1. 线程共享的内存区域(堆区, 方法区, 运行时常量池):
1. 堆(heap):
在 JVM 启动的时候创建该区域, 堆区在实际的物理内存中是可以不连续的. 我们程序中所创建的对象实例, 和数组都存放在堆区, 所以堆区是 GC(垃圾回收器)的高频工作地点, 但是当回收和使用大的内存区域时, 可能会出现性能瓶颈, 这时我们就提出疑问对象一定要放在堆区吗??? 当然是可以不放在堆区. 对此我们有两套解决方案: 逃逸分析, 栈上分配以及 TaoBaoVM 都是可以将对象放在堆区之外的解决方案, 用来提升 GC 的效率.
由于 Java 中对象的生命周期不同, 有的对象生命周期非常短暂, 有的对象声明周期甚至和 JVM 的生命周期一样, 这就使得要采用不同的 GC 算法来收集不同类型的对象. 由此分代回收算法诞生! 目前几乎所有的 GC 算法都是分代收集算法.
堆区的内存区域又可细分为: 新生代 (Young Gen), 年老代(Old Gen) 其中新生代又可按 8:1:1 分为 Eden,FromSurvivor,ToSurvivor. 堆区的参数设置大小在 JVM 启动的时候就已经设置好了, 可以通过 -Xms: 获得堆区的起始大小; -Xmx: 获得堆区的最大大小. 当超出 - Xmx(堆区的最大值)就会抛出 OOM(out of memory)Error.
- // 一个 OOM 的代码实例
- public class OOMTest {
- public static void main(String [] args){
- List<OOMObject> list=new ArrayList<>();
- while (true){
- list.add(new OOMObject());
- System.out.println("创建了一个静态对象!");
- }
- }
- static class OOMObject{
- }
- }
2. 方法区:
方法区和堆区是一样的, 也是属于线程共享的区域. 方法区中存储了每一个 Java 类的结构信息, 比如: 运行时常量池, 字段, 方法数据, 以及构造函数和普通方法的字节码内容, 和类, 实例, 接口初始化时所用到的特殊方法等数据. 在 HotSpot(是 sunJDK,openJDK 所带的 JVM, 也是目前使用最广的 Java 虚拟机)的实现中方法区的实际的物理内存位于堆中, 也就是说方法区只是逻辑上的独立.
方法区也被称为永久代, 因为方法区并不会象堆区那样进行频繁的 GC, 甚至还可以设置参数让 GC 不回收方法区, 若 GC 收集方法时也只是收集运行时常量池和类型卸载. 方法区可通过 - XX:MaxPermSize 设置内存大小进行动态内存扩展.
方法区也会发生内存溢出, 当内存大小超过 - XX:MaxPermSize 时就会抛出 OOMError.
3. 运行时常量池:
运行时常量池属于方法区, 一个有效的字节码文件中除了包含字段, 方法, 接口等信息外, 还应包括常量池表, 运行时常量池就是常量池表运行时的表示形式, 运行时常量池中可以存放多种不同的常量.
当类加载器成功的将一个类或者接口加载进 JVM, 就会分配相应的运行时常量池, 由于运行时常量池位于方法区中, 故也会发生 OOMError.
2. 线程私有的内存区域(PC 寄存器, 栈, 本地方法栈):
1.pc 寄存器:
pc 寄存器不同于物理寄存器, 更像是一个 pc 计数器, 其生命周期和线程的生命周期保持一致. PC 寄存器记录了当前字节码指令地址;
pc 寄存器是唯一一个不会发生 OOMError 的内存区域.
2.Java 栈:
栈区的生命周期和线程的生命周期一样, 栈主要用来存储栈帧, 栈帧中存储了局部变量表, 操作数栈, 以及方法出口等信息. Java 堆中存的是对象实例, 那么 Java 栈的局部变量表中存放的是各类原始数据类型, 对象引用, 以及 returnAddress(是 JVM 内部的原始数据类型)类型.
Java 栈的大小, 可以设置为固定大小, 也可设置为动态的大小. 当线程请求分配的栈容量超过 Java 栈的最大大小 JVM 就会抛出 StackOverflowError 异常.
3. 本地方法栈:
用于支持本地方法的执行, 并不是必须要的一块内存区域. 同样也会发生 OOM 和 Stack Overflow.
lk
来源: http://www.bubuko.com/infodetail-3165148.html