一、运行时内存划分
在JDK1.7中,根据Java虚拟机的规范,将其所管理的内存划分为以下这个运行时数据区域:
1. 程序计数器
当前线程所执行的字节码的行号指示器。每个线程都独立拥有一个程序计数器。
如果线程正在执行的是一个Java方法,则计数器保存正在执行的字节码指令地址,如果是Native方法则程序计数器的值则为空(Undefined)。
程序计数器是Java虚拟机规范中唯一没有规定任何OutOfMemoryError情况的数据区域。
2. Java虚拟机栈
属于线程私有,生命周期和线程相同。每个Java方法在执行时都会创建一个栈帧用于存储局部变量表(基本数据类型和对象引用)、操作数栈、动态链接、方法出口等信息。
每一个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈中会有两种异常:
- 线程请求的栈深度大于虚拟机所允许的深度,将会抛出StatckOverflowError异常
- 虚拟机栈在动态扩展时无法申请到足够的内存,就会抛出OutOfMemoryError
3. 本地方法栈
同Java虚拟机栈,不过内部执行的是Native方法
4. Java堆
Java堆是被所有线程共享的一块内存区域,用来存放对象实例。由于现在的收集器基本都采用分代收集算法,所以Java堆中还可以细分为新生代和老年代
4.1 新生代
大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。
新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。
4.2 老年代
在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。
5. 方法区
线程之间共享,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译期编译后的代码等数据。
在HotSpot虚拟机上方法区通常被实现为永久代,HotSpot选择把GC分代收集扩展至永久代,在JDK1.8之后,元空间替换了永久代,使用堆外内存存储。
6. 运行时常量池
运行时常量池属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运营师常量池中存放。
二、HotSpot内存存储
HotSpot是OpenJDK中自带的虚拟机
1. 对象的创建过程(HotSpot)
2. 对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:
- 对象头(Header):对象头存储了两部分信息:1.Mark Word(存储对象自身的运行时数据,如哈希码、锁状态标志等) 2.类型指针(对象指向它的类元数据的指针)
- 实例数据(Instance Data):程序代码中所定义的各种类型的字段内容
- 对齐填充(Padding):并不是必然存在的,仅仅起着占位符的作用。HotSpot虚拟机要求对象的大小必须是8字节的整数倍,因此当对象实例数据部分没有对齐时,就需要通过对齐填充来补全
3. 对象的访问定位
对象访问方式取决于虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种:
三、 扩展
Q:看了很多关于JVM的介绍,运行时内存划分了一个方法区,网上又有一些文章写堆有个永久代,两者存储内容差不多,顿时糊涂。
A:方法区是JVM规范中定义的一个逻辑部分,与堆做区分。 用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。而永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。而在JDK1.8 永久代用元空间取代了。