一文带你读懂JVM

一文带你读懂JVM

技术杂谈小彩虹2021-08-24 13:47:26230A+A-

字节码

java所有的指令有200个左右,一个字节(8位)可存储256种不同的指令信息,一个这样的字节称为字节码; 起始的4个字节,即绿色框的cafe babe是Goling定义的一个魔法数,意思是Coffee Baby,其十进制为3405691582;作用:标志该文件是一个java类文件

助记符:jvm 在字节码上也涉及了一套操作码助记符,使用特殊单词来标记这些数字

编译

  • 词法解析是通过空格分隔出单词、操作符、控制符等信息,将其形成token信息流,传递给语法解析器;
  • 语法解析时,把词法解析得到的token信息流按照Java语法规则组装成一棵语法树
  • 语义分析阶段,需要检查关键字的使用是否合理、类型是否匹配、作用域是否正确;当语义分析完成之后,即可生成字节码

字节码必须同通过类加载过程加载到JVM环境后,才可以执行。三种模式:解释执行;JIT编译执行;JIT编译与解释混合执行(主流JVM默认执行模式)。混合执行模式的优势在于解释器在启动时先解释执行,省去编译时间 JIT动态编译:将热点代码转换成机器码,直接交给CPU;JIT的作业是将Java字节码动态地编译成可以直接发送给处理器指令执行的机器码

类加载过程

加载类时,使用的是Parents Delegation Model,译为双亲委派模型,这个译名有些不妥。如果意译的话,则译作“溯源委派加载模型”更加贴切

启动过程进行类的Load、Link和Init,即加载、链接、初始化

  • Load,读取类文件产生二进制流,并转化为特定的数据结构,初步校验cafe babe魔法数、常量池,文件长度、是否有父类等,然后创建对应类的java.lang.Class实例
  • Link,包括验证、准备、解析三个步骤。验证阶段是更详细的校验,比如final是否合规、类型是否正确、静态变量是否合理等;准备阶段是为静态变量分配内存,并设定默认值,解析类和方法确保与类之间的相互引用正确性,完成内存结果布局
  • Init,执行类构造器clinit方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另外一个类,在虚拟机栈中执行完毕后通过返回值进行赋值

类加载器定位具体文件并读取

  • Bootstrap,最高的一层,是在JVM启动时创建的,通常由与操作系统相关的本地代码实现,是最根基的类加载器,负责装载最核心的Java类,比如Object、System、String等;通过C/C++实现,并不存在与于JVM体系内,类加载器具有等级制度,但并非继承关系,以组合的方式来复用父加载器的功能
  • 第二层在JDK9版本中,称为Platform ClassLoader,即平台类加载器,用以加载一些扩展的系统类,比如XML、加密、压缩相关的功能等,JDK9之前的加载器是Extension ClassLoader;
  • 第三层是Application ClassLoader的应用类加载器,主要是加载用户定义的CLASSPATH路径下的类。第二三层类加载器为Java语言实现,用户也可以自定义类加载器

自定义类加载器场景

  • 隔离记载类,在某些框架内进行中间件与应用的模块模块隔离,把类加载到不同的环境
  • 修改类加载方式,类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载
  • 扩展加载源,比如重数据库、网络、甚至是电视机机顶盒进行加载
  • 防止源码泄漏,Java代码容易被编译和篡改,可以进行编译加密

内存布局

JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保障JVM的高效稳定运行。

* Heap (堆区)
存储着几乎所有实例对象,推由垃圾收集器自动回收,堆区由各子线程共享使用。设定初始值和最大值,比如-Xms256 -Xmx1024M,其中-X表示它是JVM运行参数,ms是memory start的简称,mx是memory max的简称,分别代表最小堆容量和最大堆容量,建议Xms和Xmx设置一样,避免GC后调整堆大小时带来的额外压力
对象分配: 新生代和老年代。对象生产之初在新生代,步入暮年时进入老年代,老年代也接纳在新生代无法容纳的超大对象
新生代 = 1个Eden区 + 2个Survior区。大部分对象在Eden区生成,当Eden区装满时,会触发Young Garbage Collection,即YGC;
配置 -XX:MaxTenuringThreshold 计数器参数控制新生代到老年代值。如果参数是1,新生代的Eden区直接到老年代,默认值是15,可以在Survior区交换14次之后,到老年代
如果Survivor区无法放下,或者超大对象的阈值超过上限,则尝试在老年代中进行分配;如果老年代也无法放下,则会触发 Full Garbage Collection,即FGC。如果依然无法放下,则抛出OOM.

  • Metaspace (元空间)
    JDK7及以前版本中,只有Hotsport才有Perm区,译为永久代,它在启动时固定大小,很难进行调优,并且FGC时会移动类元信息。
    在JDK8及以上版本中使用元空间代替永久代。
    区别于永久代,元空间在本地内存中分配。在JDK8中,Perm区中的所有内容中字符串常量一直堆内存,其他内容类元信息、字段、静态属性、方法、常量等都移动至元空间内
  • JVM Stack(虚拟机栈``)
    栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程,就是栈帧入栈出栈的过程。StackOverflowError表示请求的栈溢出,导致内存耗尽,通常出现递归方法中;栈帧包括局部变量表、操作栈、动态链接、方法返回地址等

局部变量表:存放方法参数和局部变量
操作栈:方法执行过程中写入和提取信息个中指令
动态连接: 每个栈帧中包含一个常量池中对当前方法的引用,目的是支持方法调用过程的动态连接
方法返回地址: 方法执行退出(正常、异常)

  • Native Method Stacks (本地方法栈)
    线程调用本地方法是,进入一个不受JVM约束的世界。本地方法通过JNI(Java Native Interface)访问虚拟机运行时的数据区,甚至调用寄存器,具有和JVM相同的能力和权限。
  • Program Counter Register (程序计数寄存器)
    存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器

总结:堆和元空间是所有线程共享的,虚拟机栈、本地方法栈、程序计数器是线程内部私有的

对象实例化

字节码角度看对象创建过程

  • New
  • DUP
  • INVOKESPECIAL

执行步骤角度

  • 确认类元信息是否存在
  • 分配对象内存
  • 设定默认值
  • 设置对象头
  • 执行init方法

垃圾回收

  • GC Roots : 对象与GC Roots之间没有直接或间接的应用关系,认为这些对象“缓死”,可被回收

作为GC Roots的对象:类静态属性中的引用对象、常量引用的对象、虚拟机栈中引用的对象、本地方法栈中引用的对象

  • 标记 - 清除算法: 从每个GC Roots出发,依次标记有引用关系的对象,最后将没有被标记的对象清除。缺点:大量空间碎片化,导致需要分配一个较大连续空间时容易出发FGC
  • 标记 - 整理算法:从GC Roots出发标记存活的对象,然后将存活对象整理到内存空间的一端,形成连续的已使用空间,最后把已使用空间之外的部分全部清理掉,这样就不会有碎片化问题
  • Mark - Copy算法: 为了能够并行的标记和整理将空间分为两块,每次只激活其中一块,垃圾回收是只需要把存活的对象复制到另外一块未激活的空间上,将未激活空间标记已激活,将已激活空间标记为未激活,然后清除原空间中的原对象。优点:减少内存空间的浪费
  • 垃圾回收器(Garbage Collector)

当前实现的垃圾回收器由数十种

  • Serial回收器,主要用户YGC的垃圾回收器,采用串行当线程的方式,其中“Stop The Word”,简称STW,即垃圾回收的某个阶段会暂停整个应用程序的执行。FGC的时间相对较长,频繁FGC严重影响性能

  • CMS回收器(Concurrent Mark Sweep Colletor)是回收停顿时间比较短,目前比较常用。它通过初始标记(Initial Mark)、并发标记(Concurrent Mark)、重新标记(Remark)、并发清除(Concurrent Sweep)

缺点: 采用“标记 - 清除算法”,会产生大量的空间碎片 措施: 为解决空间碎片问题,CMS可配置 -XX:UseCMSCompactAtFullCollection 参数,强制JVM 在FGC完成后对老年代进行压缩,执行一次空间碎片整理,这时也会引发STW。为减少STW次数,可配置 -XX:CMSFullGCsBeforeCompaction=n参数,在执行n次FGC后,JVM再在老年代执行空间碎片整理

1、3步的初始标记和重新标记阶段依然引发STW,2、4步的并发标记和并发清除两个阶段可以和应用程序并发执行,比较耗时,不影响应用程序的正常执行

  • G1 (Garbage-First Garbage Collector)

通过-XX:+UseG1GC参数启动
与CMS相比: G1具备压缩功能,能避免碎片化问题,G1的暂停时间更加可控。性能整体非常不错。
G1将Java对空间分隔成了若干相同大小的区域,即region,包括Eden、Survivor、Old、Humongous四种类型

Humongous: 是特殊的Old类型,专门放置大型对选哪个。
G1:将空间分为多个区域,优先回收垃圾最多的区域。采用"Mark-Copy",空间整合能力强,不会产生大量的空间碎片
优势:可预测的停顿时间,能够尽可能快的在指定时间内完成垃圾回收任务
JDK11,G1为默认垃圾回收器

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们