Java JVM虚拟机简介
Java JVM虚拟机运行时内存分配情况为:
-
程序计数器
-
虚拟机栈
-
本地方法栈
-
堆
-
方法区
其中 程序计数器、虚拟机栈、本地方法栈是线程私有数据区,随线程生、随线程灭。而堆和方法区是线程共享数据区
程序计数器
Java程序是多线程的,CPU在多个线程中分配执行时间段。当某一个线程被CPU挂起时,需要记录代码的执行位置,方便CPU重新执行该线程时,知道从哪一行指令接着执行,这就是程序计数器的作用。
每个线程都有私有的程序计数器,随着线程的生命周期而创建或死亡。
虚拟机栈
虚拟机栈也是私有的,与线程生命周期同步。描述Java方法执行的内存模型,每个方法执行时,JVM都会在虚拟机内创建一个栈帧。
栈帧(stack frame):用于支持虚拟机方法调用和方法执行的数据结构,一个线程中包含了多个栈帧,每个栈帧内部包含局部变量表、操作数栈、动态链接、返回地址
局部变量表:变量值的存储空间,调用方法传递的参数,方法内部创建的局部变量
操作数栈:后入先出栈(LIFO),方法的执行过程中,各种字节码指令被压入和弹出栈
动态链接:支持方法的调用过程中的动态链接
返回地址:方法执行完后正常退出,或执行过程中遇到异常,异常退出
本地方法栈:
与虚拟机栈基本相同,只不过针对的是本地native方法
在开发中如果涉及JNI可能接触本地方法多一些,有些虚拟机(Hot Spot)已经将虚拟机栈和本地方法栈合二为一了
堆(Heap):
JVM所管理内存中最大的一块,用来存放对象实例,也是Java垃圾收集器GC管理的主要区域,也叫GC堆。
按照对象存储时间不同,堆中内存分为新生代(Young)和老年代(old),其中新生代又被分为Eden和Survivor区
方法区(Method Area):
JVM里规定的一块运行时数据区,方法区主要存储已经存放JVM加载的类信息(版本、字段、方法、接口)、常量、静态变量,及时编译后的代码和数据。
GC回收机制与分代回收策略
垃圾回收机制:Java虚拟机中使用一种叫做“可达性分析”的算法对内存中已经没有用的对象(垃圾)进行回收。
可达性分析:JVM把内存中所有的对象之间的引用关系看作是一张图,通过一组名为GC Root的对象作为起点,从这些节点开始向下搜索,搜索所走的路径称为引用链,最后通过判断对象的引用链是否可达来决定对象是否可以被回收。
如上图,A/B/C/D/E 与GC Root之间都存在一条直接或间接的引用链,这代表它们与GC Root之间是可达的。因此是不能被回收的,而M/J/K之间有引用链,但并不存在一条引用链与GC Root相连,不可达,因此,当GC进行回收时,M/J/K对象就会被回收。
GC Root对象
- Java虚拟机栈-局部变量表中引用的对象
- 方法区中静态引用的对象
- 仍处于存活状态中的线程对象
- native方法中JNI引用的对象
什么时候回收?
- Allocation Failure :在堆内存中分配时,如果因为可用空间剩余不足,导致对象内存分配失败,这时系统就会触发一次GC
- System.gc():在应用层,Java开发工程师可以主动调用此方法进行一次GC
垃圾回收的算法:
- 标记清除算法(Mark and Sweep GC):从GC Root集合开始,将内存整个遍历一次,保留所有可以被GC Root直接或间接引用到的对象,而剩余的对象都当作垃圾对待并回收,过程分为两步:
- Mark标记阶段:找到内存中所有的GC Root对象,只要是和GC Root直接或间接引用到的对象标记为灰色(存活对象),否则标记为黑色(垃圾对象)
- Sweep清除阶段:当遍历完所有的GC Root之后,则将标记为垃圾的对象直接清除
- 优点:实现简单不需要将对象进行移动
- 缺点:需要中断其他组件的执行,并可能产生内存碎片,提高了垃圾回收频率
- 复制算法(Copying):将现有的内存分为两块,每次只使用其中一块,在垃圾回收时将正在使用中的内存中存活的对象复制到未被使用的内存中,之后清除正在使用的内存块中的所有对象,交换两个内存角色,完成垃圾回收
- 优点:实现简单,运行高效,不用考虑内存碎片
- 缺点:可用内存大小缩小为原来的一半,对象存活率高时会频繁进行复制
- 标记-压缩算法(Mark-Compact):从根节点开始对所有的可达对象进行一次标记,之后并不是简单的清除未标记的对象,而是将所有存活的对象压缩到内存的一端。最后清除边界外的所有空间。
- Mark标记阶段:找到内存中所有的GC Root对象,只要是和GC Root对象直接或间接相连则标记为灰色(存活对象),否则标记为黑色(垃圾对象)
- Compact压缩阶段:将剩余存活对象按顺序压缩到内存的另一端
- 优点:避免了内存碎片的产生,又不需要两块相同的内存空间,性价比高
- 缺点:所谓压缩,仍需要进行局部的对象移动,一定程度上降低了效率
JVM分代回收策略
Java虚拟机根据对象的存活周期不同,把内存划分为几块,分别为新生代、老年代,这就是JVM内存分代策略
分代回收策略的中心思想:对于新创建的对象会在新生代中分配内存,此区域的对象声明周期一般较短,如果多次回收仍然存活下来,则将它们转移到老年代中
- 当Eden区第一次满的时候,会进行垃圾回收,首先将Eden区垃圾对象回收清除,并将存活的对象复制到S0,此时S1为空
- 下一次Eden区满时,再次进行垃圾回收,此时将Eden和S0区域中所有的垃圾对象清除,并将存活对象复制到S1,此时S0为空
- 如此反复在S0和S1之间切换几次(默认15次)之后,如果还有存活对象,说明这些对象的生命周期较长,则将它们转移到老年代中