全网最好的JVM总结:有生命周期的JVM

本文详细探讨了Java中的编译器种类、编译过程、解释器与编译器的配合、字节码结构、类加载与初始化、方法区和堆内存管理,以及垃圾收集(GC)中的可达性分析和G1/CMS算法。
摘要由CSDN通过智能技术生成

1.编译

1.1 java中编译器有哪些?

  • 前端编译器 javac
  • 后台即时编译器 JIT编译器
  • 静态提前编译器 (一步到位,直接把java编译成二进制)

2.2 编译过程是怎么样?

  1. 解析与填充符号表,生成语法树 (编译原理的东西)
  2. 插入式注解处理器的处理过程,对语法树进行操作 (jvm提供了一些钩子方法对语法树进行修改)比较有名的lombok、阿里的checkStyle
  3. 字节码生成

2.3 解释器和编译器怎么搭配使用的?

  1. 当虚拟机启动之后,解释器根据预定义的规范对字节码逐行解释,解释生成对应平台的机器码
  2. 后台编译器在运行过程中,对代码进行不断的优化,常见的优化有:方法内联、去虚拟化、栈上分配
  3. 为什么要2个搭配使用?随着程序启动,解释器可以首先发挥作用,而不是等后台编译器全部编译完再执行,即时编译器后期再慢慢优化

2.字节码

2.1 字节码长什么样?

  1. 咖啡baby
  2. 大版本+小版本
  3. 常量池:常亮池中的内容大致可以这么理解:
    1. 用utf-8 info这个类型来表示字节码,比如 private Integer i 用2个utf-8 info类型 Integer、i,
    2. 然后各种属性类型、方法类型来引用这个utf-8Info, 比如 field_info 指向前面2个utf-8 info类型
  4. 属性表集合
  5. 方法表集合

image.png

2.2 字节码怎么玩

image.png

  • ASM派 ASM => Byte Buddy =>cglib
  • Javassist派 Javaassist

JDK、CGLIB、Javassist和ASM的动态代理使用对比_xiaoliuliu2050的博客-CSDN博客

3.类加载

3.1 类加载的来源

  1. 文件
  2. 网络
  3. 动态代理

3.2 类加载的过程

  1. 加载
  2. 链接
    1. 验证 验证这个类文件对不对
    2. 准备 给类变量赋初值
    3. 解析 将运行时常量池里面的符号引用转换为直接引用
  3. 初始化
  4. 使用
  5. 卸载

!!! 解析和初始化的顺序不是确定的,为了动态绑定,先初始化,再解析

3.3 哪些情况会进行类的初始化

  1. 使用new
  2. getStatic、putStatic、invokeStatic这几条字节码指令,就是说我想要操作类变量的时候,不管是属性还是方法,都会导致类加载
  3. 对类进行反射调用
  4. 初始化类的时候,如果父类没有初始化,需要初始化父类
  5. 虚拟机启动时,用户需要指定一个执行的主类(main),虚拟机就会初始化这个类

3.4 类加载和类初始化的理解

类加载不代表类初始化了,类初始化就代码类肯定加载了。XXX.class就只是加载了类,但是并没有类初始化。

3.5 几个类加载

  1. Bootstrap ClassLoader 加载核心类库,rt.jar
  2. Extension ClassLoader 拓展类加载 /jre/ext/xxx.jar
  3. Applicaiton ClassLoader 系统类加载 加载ClassPath上的类
  4. 自定义加载器

3.6 为什么要使用双亲委派

保证核心类库的安全

4.运行时数据区 jvm | ProcessOn免费在线作图,在线流程图,在线思维导图 |

image.png

4.1 方法区

4.1.1 方法区的理解

在类加载之后,把字节码静态数据转换为运行时的数据结构,比如常量池->运行时常量池;常量池之后的类、方法、属性啊 ->上图中的类信息、属性、方法信息等;另外在堆中创建一个Class对象作为方法区的入口



4.2.2 方法区存放哪些内容

  • 运行时常量池
  • 类信息 (类、属性、方法)

4.2.3 方法区的演进过程

image.png

  • 在1.8之前称为永久代,里面存放的 运行时常量池(包含字符串常量池)、类信息、静态变量等
  • 1.8之后换为元空间,将静态变量、字符串常量池放到堆里面
  • 为什么把永久代换成元空间,随着各种动态加载框架的使用,永久代空间设置很难确定,那么我们是不是可以放到jvm内存之外来分配哪,用元空间就解决了这个问题

4.2 堆

4.2.1 堆的组成结构

  • 新生代
    • Eden区
    • Survivor区
    • Eden:Survivor1: Survivor2 = 8:1:1
  • 老年代

4.3 虚拟机栈

1个线程就有1个虚拟机栈,线程方法调用就是栈帧的入栈和出栈

4.3.1 栈帧里面有什么

  • 局部变量表
  • 操作数栈 :局部变量表通过操作数栈来完成一些运算
  • 动态链接:多态的时候,想要知道这个方法到底属于哪个类的方法,所以栈帧会有一个指向运行时常量池的方法引用
  • 返回地址:存放调用方法的pc寄存器的值,我得知道上一个方法地址是多少

4.4 本地方法栈

在hotspot中,本地方法栈和虚拟机栈实现方式一样,本地方法栈调用的都是native方法

4.5 程序计数器

唯一不会出现OOM的内存区域

4.6 对象的创建过程

Car car = new Car(); car.start();

  1. 类加载:当遇到new指令时,虚拟机检查Car能否在运行时常量池中定位到它的符号引用,如果没有,那就进行类加载,结果:将类加载进方法区,堆生成一个Class对象,方便访问
  2. 对象内存分配:给对象分配一块内存,分配出现并发问题解决方案:1、CAS自旋 2、TLAB 本地分配(在eden中给每个线程分配了一块内存),结果:在Eden区中分配一块内存给Car对象
  3. 对象内存初始化:Jvm初始化分配好的内存,将其设为零值。结果:Car对象的实例都是有初始数据的
  4. **设置对象头: **1、设置markword(hashcode、GC分代年龄、锁标志、锁信息)2、设置类型指针(指向堆里面的class对象)

image.png
image.png

  1. **执行构造函数:**最后执行构造函数,对属性赋值。
  2. **Car引用指向 Car对象:**这里引用有哪些?强软(内存不够回收)弱(gc回收)虚 (随时可能回收)
  3. **创建栈帧入栈:**创建start栈帧入栈

5. GC

创建完对象,系统开始运行起来了,但是随着越来越多的对象创建并执行,内存是不是不够了,内存不够怎么办,那就是垃圾回收

5.1 怎么知道哪些对象要回收

  • 引用计数法 :可能存在循环引用,导致gc不了的问题
  • 可达性分析算法:在hotspot中使用的是可达性分析算法,从GC Roots往下往下开始遍历,看GC Roots上有没有对象引用到它,如果没有,就考虑要回收了

5.2 GC Roots哪些

  • 局部变量
  • 静态变量

5.3 怎么回收(算法)

  • 复制算法

将内存一分为二,在同一时间只会使用其中一块内存,GC的时候只要标记出存活对象,然后把存活对象移到另外一半内存中就行

  • 优点:简单快速、内存完整

  • 缺点:内存利用率低

  • 标记清除

    GC的时候标记出所有存活对象,然后把没有标记的对象全部清理掉

    • 优点:简单
    • 缺点:会产生大量的内存碎片
  • 标记整理

GC的时候标记出所有存活对象,然后把没有标记的对象清理掉,并将存活对象挪到到内存一端。

  • 优点:内存完整
  • 缺点:效率不是很高

5.4 怎么回收(实践)

有了算法,当然有对应的垃圾回收器,比如Serial、ParNew、ParallelScavenge、CMS、G1等等。
根据新生代和老年代的特点,最终是这样的:
image.png

  • 新生代 朝生夕灭,存活对象不多,所以使用复制算法
  • 老年代 对象存活对象比较多,所以使用整理或者清除算法

新生代:Serial 单线程、ParNew 多线程、Parallel 吞吐量
老年代:SerialOld 单线程、 Paralle Old、CMS

一般来说,内存小一点的用 parNew + cms, 内存大一点的就直接上g1了

5.5 CMS的工作原理

  1. 初始标记 简单的找一下 GC Roots
  2. 并发标记 GC线程和用户线程并发执行,从GC Roots开始标记需要清理的对象
  3. 重新标记 在并发执行的时候,肯定又会产生很多垃圾,所以需要重新标记需要清理的对象
  4. 并发清理 将需要清理的对象全部清理掉

也就说它把stop world处理的时间分散在和用户线程并发处理中了

5.6 CMS可能会出现的一些问题

  • CMS老年代垃圾回收的默认触发比例是92%,也就是说92%的时候会触发老年代回收,如果在并发清理的时候,又有对象进入老年代,假设老年代不够,就会使用Serial Old去Stop World来回收老年代所有对象,一旦使用Serial Old来回收内存,非常的耗时
  • CMS使用的标记清除,会存在内存碎片,所以CMS给了参数CMS的另外一个参数 -XX:+UseCMSCompactAtFullCollection,就是说在几个Full Gc之后需要执行一次内存碎片整理的

5.6 G1的工作原理

  • G1会把内存分为很多Region,默认是2048个,假设设置堆内存4G,那就是每个Region2M
  • 新生代初始占比5%,也就是大概100个region,接着JVM会不断增加Region,但是不会超过60%
  • 尽管G1取消了新生代、老年代,但是其实对于Region来说还是有逻辑的分代思想
  • 新生代里面还有Eden和Survivor,默认也是8:1:1。在创建Eden的同时也会创建Survivor。
  • 当新生代Region超过 60%,就触发垃圾回收,会把Eden的Region放到S1中Region中,不过在回收的时候需要考虑停顿时长200ms,如果设置了,就需要保证回收时长不能大于200ms
  • Region里面的新生代慢慢也会存放到老年代中

5.7 三色标记

  • 初始状态对象都是白色
  • CMS和G1在初始标记的时候,GC Roots 标记为灰色
  • 并发标记的时候,扫描整个引用链,有子节点的话,当前节点标记为黑色,子节点为灰色
  • 重复上面的过程,最后

image.pngA D2个是灰色
image.pngAD 2个变成黑色,E是灰色
image.pngEFG变成黑色,清理白色 BCH

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值