Java JVM区域划分、运行时内存、GC
一、JVM概念
基本概念:存在于操作系统之上的运行Java代码的虚拟计算机
基本划分:程序计数器/PC寄存器、虚拟机栈、本地方法栈、方法区、堆、直接内存
线程私有区域包含(即生命周期与线程相同,依赖用户线程的启动/结束,而创建/销毁JVM内):程序计数器/PC寄存器、虚拟机栈、本地方法栈
线程共享区域包含(即随虚拟机的启动/关闭而创建/销毁):方法区、堆
二、划分区域的详细介绍
程序计数器/PC寄存器:指向虚拟机字节码指令的位置
虚拟机栈:在一个线程中,每调用一个方法,创建一个栈帧,栈帧的结构包括本地变量表、操作数栈、运行时常量池引用、动态链接、方法出口等
本地方法栈:与虚拟机栈类似,虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务
方法区:JVM加载时的类信息、常量、静态变量、编译后的代码等,其中有一块重要的区域运行时常量池,存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
堆:创建的对象和数组都保存在此,是GC回收的重要区域
直接内存:不是 JVM 运行时数据区的一部分,但也会被频繁的使用,不受JVM GC管理
三、运行时内存
Java堆从GC的角度还可以细分为: 1/3堆空间的新生代(Eden区、SurvivorFrom区和SurvivorTo区)和2/3堆空间的老年代
新生代:存放新创建的对象
Eden区:存放新生对象(如果对象占用内存过大,直接分配到老年代),当此区域内存不够时触发MinorGC进行垃圾回收
SurvivorFrom区:上次MinorGC的幸存者,作为此次GC的被扫描者
SurvivorTo区:一次MinorGC的幸存者
MinorGC:过程为复制->清空->互换
- 复制:将Eden区和SurvivorFrom区的新生对象复制到SurvivorTo区,同时给这些对象年龄+1,如果对象年龄达到了老年标准,默认年龄15,则复制到老年代区,如果ServivorTo区内存不够则放到老年区
- 清空:将Eden区和SurvivorFrom区中的对象清空
- 互换:将SurvivorTo区和SurvivorFrom区互换,原ServicorTo成为下一次GC时的ServicorFrom区
老年代:存放应用程序中生命周期长的内存对象,老年代相对稳定MajorGC不会频繁执行,当MinorGC执行后有新生代的对象加入老年代之后导致内存不足时才会触发,或无法找到足够大的连续内存空间分配给较大的对象时也会触发
永久代:存放class和Meta的信息,在加载时被放入,GC不会在主程序运行期间对永久代进行清理,所以如果class加载增多会导致抛出异常,在Java8中永久代被元数据区替代,元数据区并不在虚拟机中,直接使用本地内存,因此仅受限于本地内存。
四、垃圾回收
垃圾回收首先要确定两个方向:1、如何确定垃圾;2、如何收集垃圾
确定垃圾
- 引用计数法:在Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
- 可达性分析:为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程,两次标记后仍然是可回收对象,则将面临回收。
收集垃圾
- 标记清除算法:先标记,后清除,优点简单快捷,缺点内存碎片化严重不连续
- 复制算法:将内存划分为等量的两块,每次将回收后存活的对象复制到另一块,优点不易产生碎片,缺点压缩内存空间,如果存活对象多则复制一次会降低效率
- 标记整理算法:整合上述标记清除和复制的优点,标记待回收对象空间,将存活对象移向内存一端,然后清除端边界外的对象
- 分代收集算法:将内存划分为新生代老年代,新生代频繁回收,老年代较为稳定,新生代使用复制算法,老年代为标记整理法。