Java虚拟机也就是Java Virtual Machine,简称JVM。是Java中用来运行程序的虚拟机,它可以帮助Java程序屏蔽各种操作系统指令集的差异,从而做到跨系统运行。
JVM内存区由几大部分组成
接下来我们讲解一下这几块主要的作用是什么,以及是如何相互协作的
虚拟机栈(virtual stack)
虚拟机栈主要存储栈帧,所属栈帧就是一种数据结构,包含局部变量、操作数、动态链接、方法出口。在jvm中每一个线程都拥有一个栈帧。
本地方法栈(Native method stack)
本地方法栈跟虚拟机栈类似,区别就在于本地方法一般是被Java程序用到并且被native修饰的方法,这些方法不是用Java语言写的代码,但是也需要分配内存空间来运行,所以本地方法栈就是用来存放这些栈帧的。
程序计数器(PC计数器)
程序计数器是线程私有的,它是一个指针,指向当前线程下一条代码的内存空间地址。当cpu调度算法将执行权交给其他线程时,有了程序计数器才能恢复到之前的操作。
方法区
方法区主要存储常量、静态变量、类型信息等。
堆(heap)
堆中存储的是程序在运行过程中产生的对象,这篇blog重点想讲的就是JVM中的堆,因为堆所占的内存最大,垃圾回收机制(GC)回收的就是堆中的对象,它也是jvm调优的重点对象。堆的组成分为年轻代(young heap)和老年代(old heap),而年轻代中又分Eden(伊甸园)区和Survival(幸存者)区,在年轻代这里Java会开一个线程,叫Minor GC, 专门用来处理没有被使用的垃圾对象,它利用可达性分析算法来推算对象是否是垃圾,原理大概是:每次Minor GC周期来临时,如果Eden的内存被占满,那么Minor GC就会找到每个在栈中的变量,名叫GC roots,通过GC root指向的堆内存地址找到相应的对象,然后一步一步找,找到那些没有被任何其他对象引用的对象,把它们标记为垃圾,然后回收掉,那些没有被标记为垃圾的则会进入幸存者区,并且年龄+1岁,当幸存者区的对象达到了15岁(默认是15,可以修改)之后,GC会把它移到老年代中,而到了老年代中,只有当老年代的内存被占满了之后才会触发一次Full GC,Full GC是将所有的年轻代和老年代中的GC roots扫描一遍,一般来说Full GC的执行时间是Minor GC的10倍,而每次GC程序执行时都会STW(stop the world),如果Full GC太多的话,用户体验肯定是不好的,所以才要jvm调优。
堆的组成如下图
在我的理解中,为什么绿色框叫Eden。因为JVM的设计者是歪果仁,然后西方神话中伊甸园是人类诞生的地方,也就是亚当夏娃偷吃禁果的地方,然后因为自然灾害啊饥荒之类的,导致很多人灭绝,然后存活下来的人就是幸存者,最后经过重重考验才能步入老年,而步入老年的概率是极低的。对应Java堆模型的分析,GC的垃圾回收线程就是自然灾害,每经过一段时间就会发生一次,只有经得住GC筛选的才能活下来。从这里可以看出底层设计者的高明之处。
从上述堆模型和内存回收机制我们可以看出,因为每次GC都会stop the world,Minor GC因为保存的对象并不多,所以基本是无感的,对于小型系统来说。但是Full GC会对整个GC roots进行扫描,stop the world的时间较长,用户就会感知到这种卡顿。除了15岁后GC会把对象放入老年代以外,还有一种动态对象年龄判定机制,即要从Eden中幸存到Survival的对象总大小大于Survival的内存空间的50%时,会直接移入老年代,所以尽量是对象在进入老年代之前能被销毁掉。也就是对象能放在幸存者中等待下一次GC回收,而不是进入老年代。