JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。作为一个Java开发者,这是必须要掌握的知识点。
一,JVM的基本结构
JVM启动后,对操作系统来说,JVM是一个的进程,这个进程的基本结构如上图所示。它包括:
运行时数据区是JVM从操作系统申请来的堆空间和操作系统给JVM分配的栈空间的总称。也是JVM内存结构所在位置。
二,JVM的内存模型
根据java虚拟机规范,java虚拟机管理的内存将分为下面五大区域。分为:虚拟机栈,堆,方法区,程序计数器,本地方法栈。
1,程序计数器
是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Natice方法,则为空。
2,Java 虚拟机栈
它是线程私有的。每个方法在执行的时候也会创建一个栈帧,存储了局部变量,操作数,动态链接,方法返回地址。每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。通常所说的栈,一般是指在虚拟机栈中的局部变量部分。局部变量所需内存在编译期间完成分配,如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
3,本地方法栈
线程私有,和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。也会抛出StackOverflowError 和OutOfMemoryError。
4,方法区
被所有方法线程共享的一块内存区域。用于存储已经被虚拟机加载的类信息,常量,静态变量等。这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。
5,堆内存
堆是JVM内存占用最大,管理最复杂的一个区域。唯一的途径就是存放对象实例:所有的对象实例以及数组都在堆上进行分配。jdk1.7以后,字符串常量从永久代中剥离出来,存放在堆中。堆具有进一步的内存划分。即新生代和老年代,划分如下图所示:
按照GC分代收集的角度堆内存划分:
堆内存中的内存分配:
堆内存中年轻代和老年代的内存分配及使用规则:
1,新创建的对象主要分配在年轻代的Eden区;
被new的对象优先分配到年轻代的Eden区,如果Eden区没有足够大小的连续内存空间分配时,Jvm就会触发一次minor GC,经过minor GC回收存活下来的对象被分配到Survivor区,如果Survivor区不够安置,Jvm会通过“空间分配担保机制”提前转移对象到老年代。
2,大对象直接进入老年代
那些需要大量连续内存空间的对象,如数组或很长的字符串,大小超过虚拟机提供的阙值-XX:PretenureSizeThreshold(默认值为0)时,对象直接分配到老年代,避免在新生代Eden和两个Survivor区之间发生大量的内存复制(新生代采用复制算法进行垃圾回收)。
3,年轻代长期存活的对象会被晋升到老年代
虚拟机内存回收时,为了能够识别哪些对象该继续留在新生代(Survivor区),那些对象应该转移到老年代,它给每个对象定义了一个年龄计数器。每当对象在Survivor区的From<--->To区复制一次,年龄计数器就+1,对年龄超过 -XX:MaxTenurigThreshold设置的值时,对象被转移到老年代。对象年龄最大值为15。注意,这里不是所有对象的年龄都到设置的阙值才转移,当Survivor区空间内存不足时,对象也会提前进去老年区。
4,动态对象年龄判断
虚拟机不是永远等到对象年龄达到阙值后才转移到老年代。当Survivor中相同年龄的所有对象的大小总和大于Survivor的一半时,那些年龄大于等于此相同年龄的所有对象将会直接提前进入老年区。
注:-XX:MaxTenurigThreshold未设置时,默认为15。