学习java虚拟机 - 内存布局
一、是什么
内存是非常重要的系统资源, 是硬盘和CPU的中间仓库及桥梁, 承载着操作系统和应用程序的实时运行.
JVM内存布局规定了Java在运行过程中内存申请丶分配丶管理的策略,保证了JVM的高效稳定运行.
JVM一般讲内存划分为5个区域,分别是堆区(Heap), java虚拟机栈(jvm Stacks), 本地方法栈(Native Method Stacks), 程序计数器(Program Counter), 元数据区(Metaspace),
其中元数据区是jdk1.8才有的.
二、怎么样
1. 堆区(Heap)
堆区几乎保存了所有的实例对象, 由各子线程共享。 垃圾回收主要发生在堆区。堆区根据对象的生命周期以及垃圾回收算法, 划分为新生代和老年代两大块。
1.1) 新生代
新生代主要使用“标记-复制”的垃圾回收算法, 所以新生代一般分为空间较大的Eden区和两个空间较小的Survivor区。新生代=1个Eden区+2个Survivor区。 在运行期间, 一块Survivor区是空闲的, 当发生垃圾回收时, Eden区和另一块Survivor区中还存活的对象, 就会复制到空闲的Survivor区。
1.2) 老年代
新生对象达到一定条件是可以晋升到老年代的. 如下图所示, 新生对象, 会先尝试在Eden存放, 如果放不下, 则触发Young GC, 存活的对象会复制到空闲的幸存者区, 存活超过一定次数之后的对象会晋升到老年代. 经过Young GC之后, 会再次尝试在Eden存放之前的新生对象, 如果还是存放不下, 就尝试在老年代存放. 如果在老年代存放不下,则触发Full GC, Full GC之后还是存放不下, 则报OOM的错误.过程如下图:
2. 元空间(Metaspace)
元空间是hotspot虚拟机 jdk8 才出现的。它的前身是perm区,被译为永久代。无论是元空间,还是永久代,它都是java虚拟机规范中方法区的实现。方法区(Method Area) 与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量,即使编译编译器编译后的代码等数据。
元空间与永久代不同,元空间使用的是堆外内存(直接内存),而永久代使用的是堆内存。在JDK8里,永久代中的所有内容中的字符串常量移至堆内存,而类元信息、字段、常量、静态变量、方法等都移至到了元空间。
3. 虚拟机栈(JVM Stacks)
java虚拟机栈是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法返回地址。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
4. 本地方法栈(Native Method Stacks)
本地方法栈和虚拟机栈作用类似,区别在于,虚拟机栈执行的是java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
5. 程序计数器(Program Counter Register)
程序计数器可以看做是当前线程执行字节码的行号指示器。在虚拟机概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。在任何一个确定的时刻,一个处理器(对于多喝处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。这类内存区域为"线程私有"的内存。
如果线程正在执行的是一个JAVA方法,计数器记录的是正在执行的虚拟机字节码指令地址,如果正在执行的是native方法,这个计数器值为空。
学习资料
《码出高效 Java开发手册》
《深入理解Java虚拟机》