JVM内存结构
目录一、官网:二、JVM内存结构2.1类加载器2.2运行时数据区2.2.1程序计数寄存器(Program Counter Register / PC Register)2.2.3方法区概念:运行时常量池:方法区的回收:元数据区:2.2.3Java虚拟机栈:概念:栈帧:局部变量表(Local Variables Table):操作数栈(Operand Stack):动态连接方法返回地址2.2.4本地
·
目录
2.2.1程序计数寄存器(Program Counter Register / PC Register)
2.2.4本地方法栈(Native Method Stack):
3.2即时编译器 (Just In Time Compiler / JIT编译器):
一、官网:
- https://docs.oracle.com/javase/specs/index.html
- https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
二、JVM内存结构
2.1类加载器
2.2运行时数据区
2.2.1程序计数寄存器(Program Counter Register / PC Register)
- 程序计数寄存器,简称程序计数器。
- 程序计数器是当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。
- 程序计数器是线程私有的。每条线程都需要有一个独立的程序计数器,各条线程间的计数器互不影响,独立储存。
- 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;每当执行完一项指令操作后,程序计数器就会将值改为下一条需要被执行的指令地址。如果正在执行的是native方法,这个计数器值则为空。
2.2.3方法区
概念:
- 用于储存虚拟机已加载的类信息、静态变量信息、常量信息、即时编译器编译后的机器指令。
- 各个线程共享方法区,方法区又叫做永久代(Permanent Generation)。
运行时常量池:
- 运行时常量池是方法区中存储常量信息的地方。这些常量包括类的引用、字段的引用、方法的引用、字符串常量等信息。
- 字符串常量池:
- 存储字符串常量的存储空间,属于运行时常量池的一部分。
- jdk7之前字符串常量池是方法区的一部分。jdk7中将字符串存储在堆中了,字符串常量池中仅仅存储了对应的引用而已。
方法区的回收:
- 对废弃的常量、无用的类进行回收:
- 废弃常量:没有任何引用关联着的常量即废弃常量。
- 无用的类:同时满足以下3个条件的类
- 该类所有的实例都已经被回收,也就是说Java堆中不存在该类的任何引用。
- 加载该类的ClassLoader已经被回收。
- 该类对应的Class对象没有再任何地方被引用,无法在任何地方通过反射访问该类的方法。
- 参数:
- 满足条件的类是否会被回收,由虚拟机设定:由 -Xnoclassgc 参数进行控制。
- 在大量使用反射、动态代理(jdk动态代理、cglib动态代理等)、动态生成jsp的场景中都需要虚拟机具备类卸载功能,以保证方法区不会溢出。
元数据区:
- 概念:jdk8中取消了方法区,取而代之的是"元数据区",元数据区并不在JVM中,而是在于本地内存中的。
2.2.3Java虚拟机栈:
概念:
- Java虚拟机以方法作为最基本的执行单元,栈帧(Stack Frame) 则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual M achine
Stack)的栈元素。 - Java虚拟机栈是线程私有的,它的生命周期与线程相同。
栈帧:
- 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于储存局部变量表、操作数栈、动态连接、方法返回地址等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈道出栈的过程。
- 对于执行引擎来讲,在活动线程中,只有位于栈顶的方法才是在运行的,只有位于栈顶的栈帧才是生效的,其被称为当前栈帧,与这个栈帧所关联的方法被称为当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
局部变量表(Local Variables Table):
- 是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
操作数栈(Operand Stack):
概念:
- 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会不断地往操作数栈中添加或获取操作数,这些操作数将用来进行算数运算 或 在调用其它方法时进行方法参数的传递。
- 操作数栈又被称为操作栈。
举例:
- 整数加法的字节码指令 iadd,这条指令在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会把这两个int值出栈并相加,然后将相加的结果重新入栈。
动态连接
- 每个栈帧都包含一个指向运行时常量池中该栈帧关联的方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
方法返回地址
- 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:
- 恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
2.2.4本地方法栈(Native Method Stack):
- 概念:为虚拟机使用到的Native方法服务。
- 说明:与虚拟机栈的区别:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。Sun HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。
2.2.5堆:
- 概念:是被所有线程共享的一块内存区域,在虚拟机启动时创建。唯一的目的就是存放对象实例。
- Java堆分为新生代、老年代
- 新生代(New generation 或 Young generation)
- 结构:Eden空间、From Survivor空间、To Survivor空间
- 特点:新生代中大部分的对象是“朝生夕死”的,每次垃圾收集时都发现有大批对象死去,只有少量存活。
- 老年代(Old generation 或 Tenured generation)
- 特点:老年代中的对象存活率高、没有额外空间对它进行分配担保。
- 新生代(New generation 或 Young generation)
三、执行引擎
概念:
- 将字节码指令解释或编译为对应平台的本地机器指令,通过字节码和执行引擎,java具备了跨平台的特性。
3.1解释器(interpreter)
概念:
-
将每行字节码“翻译”为对应平台的本地机器指令。
-
当一条字节码指令被解释执行完成后,接着再根据程序计数器中记录的下一条需要被执行的字节码指令执行解释操作。
优点:
- 服务启动快:当程序启动后,解释器可以马上发挥作用,省去编译的时间,对于大多数代码只会执行一次的情况比较适合。
- 解释执行在编译器进行激进优化不成立的时候,作为后备编译器来执行代码。
缺点:
- 解释执行的效率比较低下,故jvm后面又提供了即时编译执行的能力。
3.2即时编译器 (Just In Time Compiler / JIT编译器):
概念:
- JIT编译器在运行时会针对那些频繁被调用的“热点代码”做出深度优化,并将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。由于这种编译方式发生在方法的执行过程中,因此也被称之为栈上替换,或简称为OSR (On StackReplacement)编译。
- JVM中编译器
优点:
- 编译模式启动慢,但是后期执行速度快,
缺点:
- 比较占用内存,因为机器码的数量至少是JVM字节码的十倍以上,这种模式适合代码可能会被反复执行的场景。
热点代码
概念:
- 一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为“热点代码”
热点代码的判断:
- HotSpot VM所采用的热点探测方式是基于计数器的热点探测,HotSpot VM为每一个方法都创建一个调用计数器(Invocation Counter)和一个回边计数器(BackEdge Counter):
- 方法调用计数器:
- 用于统计方法的调用次数。
- 默认阈值在Client模式下是1500次,在Server模式下是10000次。超过这个阈值,就会触发JIT编译。这个阈值可以通过虚拟机参数-XX :CompileThreshold来人为设定。
- 当一个方法被调用时, 会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
- 热度衰减:
- 如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度, 如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay) ,而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。
- 进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数 -XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。
另外, 可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。
- 回边计数器:
- 用于统计循环体执行的循环次数。
- 在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge)。
热点代码的存储:
- 热点代码片段经过即时编译后的产物--机器指令,需要缓存起来Code Cache,存放在方法区。
注意:
- 机器在热机状态可以承受的负载要大于冷机状态。如果以热机状态时的流量进行切流,可能使处于冷机状态的服务器因无法承载切换过来的流量而挂掉。故部署服务时我们往往分多个批次来进行部署。
3.3HotSpot JVM的执行模式:
jdk7、jdk8中默认采用解释器与即时编译器并存的混合模式:
- 在混合模式下,当jvm启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,这样可以省去许多不必要的编译时间。随着时间的推移,即时编译器发挥作用,把越来越多的代码编译成本地代码,获得更高的执行效率。
参数:
- -Xint: 采用解释器模式执行程序,执行一行JVM字节码就将其编译为对应平台的本地机器指令。
- -Xcomp:采用即时编译器模式执行程序,先将所有JVM字节码一次性编译为机器指令,如果即时编译出现问题,解释器会介入执行。
- -Xmixed:采用混合模式执行程序,最开始使用解释模式执行代码, 后面针对部分“热点代码”采用编译模式执行。HotSpot VM默认该模式执行程序。
3.4垃圾回收
四、本地接口、本地库
更多推荐
已为社区贡献6条内容
所有评论(0)