深入理解JVM

JVM总览

在这里插入图片描述

  • JVM分为三大类,类加载子系统,运行时数据区和执行引擎
  • 其中类加载子系统是用来加载编译过的class文件,然后把class元数据信息放入运行时数据区,再有执行引擎执行
  • 本文基于HotSpot虚拟机,JDK1.8

运行时数据区

  • 堆是线程共享的,但是每个线程也有一小块私有区域叫TLAB,线程创建对象会先放在这个区域,避免分配内存冲突
  • 大部分的对象都会分配在堆中,堆是虚拟机中最大的一块区域
  • 堆中又划分为新生代和老年代
  • 新生代又分为Eden区和两个Survivor区,分别是From区和To区
虚拟机栈
  • 虚拟机栈是线程私有的,跟着线程一起创建和销毁
  • 栈是先进后出的
  • 虚拟机栈中会放置栈帧,而栈帧就是一个个的方法,每次调用方法就会放入一个栈帧,方法执行完毕就会弹出一个栈帧
  • 栈帧中包含局部变量表,操作数栈,动态链接和返回地址等
  • 局部变量表中包含了当前方法使用的所有变量
  • 操作数栈就是用来计算信息,比如变量a加b,就是a出栈b出栈然后计算出c在入栈
  • 动态链接就是指向运行时常量池,可以让方法把符号引用改成直接引用,比如调用了别的方法,在加载时会把这个方法调用加载成符号引用,这时可以用过运行时常量池找到对应的直接引用
本地方法栈
  • 本地方法栈也是线程私有的,与虚拟机栈类似
  • 本地方法栈是用来调用本地方法的栈,本地方法是C代码,不是JAVA
程序计数器
  • 程序计时器也是私有的
  • 主要用来记录执行到哪一行,因为CPU切换上下文需要知道执行到了哪里,以便接着执行
元空间
  • 元空间是共享的,存在于非堆空间
  • 元空间是在JDK1.8以后才引进的概念,以前都叫方法区
  • 元空间主要包含了加载的类信息,字符串常量池,运行时常量池等

类加载子系统

  • 虚拟机加载类经过加载,链接和初始化等步骤,链接又分为验证,准备和解析
加载:
  • 创建某个类的实例或者被引用时这个类如果没有被虚拟机加载,则虚拟机会加载这个类。本地类库会被引导类加载器(Bootstrap ClassLoader)加载,扩展类库由扩展类加载器(ExtClassLoader)加载,一般的类比如我们自己写的类就会由应用程序类加载器(AppClassLoader)加载
    • 双亲委派机制:在加载器加载类时,会优先给自己的父类加载,直到引导类加载器,如果父类不加载,则才会自己加载。这种机制是为了保证类库的正常使用,比如说我们也自己定义了一个java.lang.String,如果是应用程序加载器加载就会出现问题
    • 破坏双亲委派机制:实现ClassLoader接口,重写loadClass方法,不调用super.loadClass()
验证:
  • JVM验证编译的代码是否语法没有错误,是否会危害虚拟机等
准备:
  • 开始为静态变量分配内存,设初始值。如果是常量则会直接赋值
  • 会为非私有的实例方法创建一个数组类型的方法表,这样就可以实现动态方法。也就是在通过父类new出子类并调用方法时,通过动态的类型来调用方法表,获取对应的实例方法并调用
解析:
  • 将编译期的符号引用通过运行时常量池找到对应的地址,然后修改为直接引用
初始化:
  • 虚拟机会把所有需要赋值的语句全部放在构造器</clinit/>()中,比如对静态变量赋初值等

执行引擎

  • JVM中执行引擎分为解释器和即时编译器(JIT)
解释器
  • 解释器是一行一行的代码解释执行,因为每次执行都需要解释执行,所以执行效率不是很高,但是胜在拿来即用,虚拟机刚开始运行时就是先解释执行,然后再由JIT对热点代码编译成机器码执行在硬件上
即时编译器
  • 即时编译器分为两种,C1和C2,C1适用于时间短,启动快的场景,C2适用时间长,性能要求比较高的场景。根据场景也叫Client Compiler 和 Server Compiler
  • 在JDK1.7时引入了分层编译,1.8中默认开启,其中分为5个层次
  1. 解释执行
  2. C1编译,但是不开启Profiling(性能检测功能)
  3. C1编译,仅开启Profiling的方法调用次数和循环回边执行次数
  4. C1编译,开始所有Profiling
  5. C2编译
  • 虚拟机对热点方法的定义有两种
  1. 方法调用计数器:统计方法的调用次数,C1是1500次,C2是10000次,次数达到时就会触发JIT,分层情况下虚拟机会动态调整
  2. 循环回边计数器:统计循环代码体执行次数,分层情况也会动态调整
编译优化
  • 在编译器编译时,会对代码进行优化,比如方法内联,逃逸分析等
  • 方法内联:对于方法体不是太大的方法,虚拟机可以直接把被调用方法转换成当前方法中的代码,这样可以避免被调用方法的入栈出栈等操作
  • 逃逸分析:对于一个方法中的对象,我们可以判断这个对象是否会逃逸,也就是会不会作用到别的作用域。如果在当前方法创建了一个对象,这个对象并没有传给别的方法等,只是作用在这个方法,那么我们就称这个对象没有逃逸
    • 基于逃逸分析的优化有锁消除,标量替换等
    • 锁消除:对不会进行线程竞争的锁会被优化掉。比如synchronized(new String),这样就一定不会线程竞争
    • 标量替换:把一个对象替换成对应的局部变量,这样就可以避免在堆里创建对象。比如一个Student类中只有一个int age,一个String name这两个变量,那么就不创建这个对象,在局部变量表中添加这两个字段,然后跟栈一起被回收
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值