1.编译
1.1 java中编译器有哪些?
- 前端编译器 javac
- 后台即时编译器 JIT编译器
- 静态提前编译器 (一步到位,直接把java编译成二进制)
2.2 编译过程是怎么样?
- 解析与填充符号表,生成语法树 (编译原理的东西)
- 插入式注解处理器的处理过程,对语法树进行操作 (jvm提供了一些钩子方法对语法树进行修改)比较有名的lombok、阿里的checkStyle
- 字节码生成
2.3 解释器和编译器怎么搭配使用的?
- 当虚拟机启动之后,解释器根据预定义的规范对字节码逐行解释,解释生成对应平台的机器码
- 后台编译器在运行过程中,对代码进行不断的优化,常见的优化有:方法内联、去虚拟化、栈上分配
- 为什么要2个搭配使用?随着程序启动,解释器可以首先发挥作用,而不是等后台编译器全部编译完再执行,即时编译器后期再慢慢优化
2.字节码
2.1 字节码长什么样?
- 咖啡baby
- 大版本+小版本
- 常量池:常亮池中的内容大致可以这么理解:
- 用utf-8 info这个类型来表示字节码,比如 private Integer i 用2个utf-8 info类型 Integer、i,
- 然后各种属性类型、方法类型来引用这个utf-8Info, 比如 field_info 指向前面2个utf-8 info类型
- 属性表集合
- 方法表集合
2.2 字节码怎么玩
- ASM派 ASM => Byte Buddy =>cglib
- Javassist派 Javaassist
JDK、CGLIB、Javassist和ASM的动态代理使用对比_xiaoliuliu2050的博客-CSDN博客
3.类加载
3.1 类加载的来源
- 文件
- 网络
- 动态代理
3.2 类加载的过程
- 加载
- 链接
- 验证 验证这个类文件对不对
- 准备 给类变量赋初值
- 解析 将运行时常量池里面的符号引用转换为直接引用
- 初始化
- 使用
- 卸载
!!! 解析和初始化的顺序不是确定的,为了动态绑定,先初始化,再解析
3.3 哪些情况会进行类的初始化
- 使用new
- getStatic、putStatic、invokeStatic这几条字节码指令,就是说我想要操作类变量的时候,不管是属性还是方法,都会导致类加载
- 对类进行反射调用
- 初始化类的时候,如果父类没有初始化,需要初始化父类
- 虚拟机启动时,用户需要指定一个执行的主类(main),虚拟机就会初始化这个类
3.4 类加载和类初始化的理解
类加载不代表类初始化了,类初始化就代码类肯定加载了。XXX.class就只是加载了类,但是并没有类初始化。
3.5 几个类加载
- Bootstrap ClassLoader 加载核心类库,rt.jar
- Extension ClassLoader 拓展类加载 /jre/ext/xxx.jar
- Applicaiton ClassLoader 系统类加载 加载ClassPath上的类
- 自定义加载器
3.6 为什么要使用双亲委派
保证核心类库的安全
4.运行时数据区 jvm | ProcessOn免费在线作图,在线流程图,在线思维导图 |
4.1 方法区
4.1.1 方法区的理解
在类加载之后,把字节码静态数据转换为运行时的数据结构,比如常量池->运行时常量池;常量池之后的类、方法、属性啊 ->上图中的类信息、属性、方法信息等;另外在堆中创建一个Class对象作为方法区的入口
4.2.2 方法区存放哪些内容
- 运行时常量池
- 类信息 (类、属性、方法)
4.2.3 方法区的演进过程
- 在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();
- 类加载:当遇到new指令时,虚拟机检查Car能否在运行时常量池中定位到它的符号引用,如果没有,那就进行类加载,结果:将类加载进方法区,堆生成一个Class对象,方便访问
- 对象内存分配:给对象分配一块内存,分配出现并发问题解决方案:1、CAS自旋 2、TLAB 本地分配(在eden中给每个线程分配了一块内存),结果:在Eden区中分配一块内存给Car对象
- 对象内存初始化:Jvm初始化分配好的内存,将其设为零值。结果:Car对象的实例都是有初始数据的
- **设置对象头: **1、设置markword(hashcode、GC分代年龄、锁标志、锁信息)2、设置类型指针(指向堆里面的class对象)
- **执行构造函数:**最后执行构造函数,对属性赋值。
- **Car引用指向 Car对象:**这里引用有哪些?强软(内存不够回收)弱(gc回收)虚 (随时可能回收)
- **创建栈帧入栈:**创建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等等。
根据新生代和老年代的特点,最终是这样的:
- 新生代 朝生夕灭,存活对象不多,所以使用复制算法
- 老年代 对象存活对象比较多,所以使用整理或者清除算法
新生代:Serial 单线程、ParNew 多线程、Parallel 吞吐量
老年代:SerialOld 单线程、 Paralle Old、CMS
一般来说,内存小一点的用 parNew + cms, 内存大一点的就直接上g1了
5.5 CMS的工作原理
- 初始标记 简单的找一下 GC Roots
- 并发标记 GC线程和用户线程并发执行,从GC Roots开始标记需要清理的对象
- 重新标记 在并发执行的时候,肯定又会产生很多垃圾,所以需要重新标记需要清理的对象
- 并发清理 将需要清理的对象全部清理掉
也就说它把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 标记为灰色
- 并发标记的时候,扫描整个引用链,有子节点的话,当前节点标记为黑色,子节点为灰色
- 重复上面的过程,最后
A D2个是灰色
AD 2个变成黑色,E是灰色
EFG变成黑色,清理白色 BCH