JVM结构 JVM总体结构图
类加载子系统与方法区:类加载子系统负责从文件系统和网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。
除了类信息外,方法区中还可能会存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
Java堆:java堆在虚拟机启动时建立,它是java程序最主要的内存工作区域。
几乎所有的java对象实例都存放在java堆中。堆空间是所有线程共享的。
直接内存:java的NIO库允许使用直接内存。
直接内存是在java堆外的、直接向系统申请的内存空间。
通常访问直接内存的速度会优于java堆。
出于对性能的考虑,读写频繁的场合可能会考虑使用直接内存。
由于直接内存在java堆外,因此它的大小不会受限于Xmx指定的最大堆大小,但是系统内存是有限的,java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
垃圾回收系统:垃圾回收器可以对方法区、java堆和直接内存进行回收。其中,java堆是垃圾收集器的工作重点。
和C/C++不同,java中所有的对象空间释放都是隐式的,java中没有类似free()或者delete()这样的函数释放指定的内存区域。
对于不再使用的垃圾对象,垃圾回收系统会在后台查找、标识并释放对象,完成java堆、方法区和直接内存中的全自动化管理。
java栈:每一个java虚拟机线程都有一个私有的java栈。
一个线程的java栈在线程创建的时候被创建。
java栈中保存着帧信息,保存着局部变量、方法参数,同时和java方法的调用、返回密切相关。
本地方法栈:和java栈非常类似。
java栈用于方法的调用,而本地方法栈则用于本地方法的调用。
作为对java虚拟机的重要扩展,java虚拟机允许java直接调用本地方法(通常使用C编写)
PC(Program Counter):PC寄存器也是每一个线程私有的空间。
java虚拟机会为每一个java线程创建PC寄存器。
在任意时刻,一个Java线程总是在执行一个方法,这个正在被执行的方法称为当前方法。如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令。如果当前方法是本地方法,那么PC寄存器的值就是undefined。
执行引擎:负责执行虚拟机的字节码。
现代虚拟机为了提高执行效率,会使用即使编译(just in time)技术将方法编译成机器码后再执行。
JVM堆结构及分代
堆内存是java虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,程序中所有的对象实例都存放在堆内存中。
给堆内存分代是为了提高对象内存分配和垃圾回收的效率。
如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,所花费的时间代价是巨大的,也会严重影响GC效率。
Java虚拟机根据对象存活的周期不同,把堆内存分为新生代、老年代和永久代(于JDK8中把存放元数据中的永久内存从堆内存中移到了本地内存(native memory))
新创建的对象会在新生代中分配内存,经过多次回收仍然存活下来的对象放在老年代。
新生代中的对象存活时间短,需要频繁地进行垃圾回收以保证无用对象尽早被释放掉;老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收。不同年代采用各自合适的垃圾回收算法,可以大大提高回收效率。
新生代(Young Generation)
新生代主要存放新生成的对象,内存大小相对会比较小,垃圾回收会比较频繁。且垃圾回收效率高,通常进行一次垃圾收集一般可以回收70% ~ 95%的空间。
HotSpot JVM把新生代代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to),默认比例是 8:1:1。这样划分的目的是因为HotSpot采用复制算法来回收新生代。(复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。)新生成的对象在Eden区分配(一些大对象除外),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。