Bootstrap

JVM内容

JVM虚拟机内容

1.什么是jvm (jvm就是一套规范)

定义:jvm就是java程序的运行环境(二进制字节码的运行环境)
好处:1. 一次编写,到处使用(跨平台)
2. 内存自动管理,垃圾回收功能
3. 数组下标越界检查(防止越界覆盖其他代码的内存空间)
4. 多态
比较: JDK(jvm+基础类库+编译工具)-----> JRE(jvm+基础类库)---------->jvm

2. jvm的内存结构

在这里插入图片描述

2.1 程序计数器

  1. java源代码 — jvm指令 — 解释器(程序计数器) ----机器码 — CPU

  2. 程序计数器会记录下一条jvm指令的地址,每次执行,解释器都会去程序计数器中获取下一条的指令地址(CPU中的寄存器作为程序计数器)

  3. 特点:
    计数器是线程私有的,属于单独线程的,每个线程都有自己的程序计数器
    程序计数器不会存在内存溢出的情况

2.2 栈

  1. 线程运行需要的内存空间就是栈
  2. 每个方法运行时需要的内存叫做栈帧
  3. 每个栈有多个栈帧组成,对应着每次方法调用时所有的内存
  4. 每个线程中只有一个活动栈帧,对应的就是正在执行的那个方法
  5. 特点: 先进后出
  6. 垃圾回收不会涉及到栈内存(垃圾回收是回收堆内存,而栈内存中保持先进后出的特点,每个方法执行完,随之产生的栈帧也会走出栈内存,所以不会有垃圾回收)
  7. 栈内存分配的越大,可能程序多线程数就会越小,物理内存/栈内存=线程数
  8. 方法内的局部变量不一定是线程安全的
    (1. 如果方法内局部变量没有逃离方法的作用访问,他就是线程安全的 )
    ( 2. 如果局部变量引用了对象,并且逃离了方法的作用方法,那他就是线程不安全的)
  9. 栈内存溢出 java.lang.StackOverflowError
    (1.栈帧过多导致内存溢出 2.栈帧过大导致内存溢出)
  10. 查看cpu占用过高
    用top定位到哪个进程对cpu占用过高
    ps H -eo pid,tid,%cpu | grep 进程id(定位哪个线程引起的cpu占用过高)
    jstack 进程id(可以根据线程id找到问题,进一步定位到源代码行号)

2.3 本地方法栈

  1. 给本地方法运行提供内存

2.4 堆

  1. 通过new创建的对象都会使用堆内存
  2. 特点:
    他是线程共享,堆中对象都要考虑线程安全
    堆是有垃圾回收机制的
  3. 堆内存溢出问题:java.lang.OutOfMemoryError
    程序中不停的循环new一个对象就会导致堆内存溢出
    使用 jvitsalvm工具排查
    使用 jconsole 工具排查

2.5 方法区

  1. 方法区就是个概念,虚拟机启动时创建,存储类的数据
  2. 1.6之前用的永久代,1.8以后用的元空间

2.6 常量池

  1. 常量池就是一张表,虚拟机指令根据这张表找到要执行的类名,方法名,参数类型等信息
  2. 运行时常量池,当类被加载时,常量池信息机会放到运行时常量池,并把符号地址换成真实地址

2.7 SpringTable

  1. 长度固定,不可扩容,串池中的数据是唯一不重复的
  2. 常量池中的字符串仅是符号,第一次用到才变为对象
  3. 字符串变量拼接原理是StringBuilder,常量拼接原理是编译期优化
  4. 可以使用intern方法,主动将串池中没有的字符串对象放入串池
    1.8将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回
  5. 1.6中stringtable在常量池中,使用永久代空间,1.8中stringtable在堆内存中,使用堆内存的垃圾回收机制,减少内存的压力
  6. 性能调优:
    调整 -XX:StringTableSize=桶个数 适当增加hasp的长度,减少耗时
    考虑是否将字符串对象入池

2.8 直接内存

  1. 定义:不属于jvm虚拟机内存,而是系统内存。java代码和系统都可以访问到的区域。
    用于数据缓冲区,回收成本高,性能快,不会被垃圾回收。
  2. 释放原理:
    底层使用unsafe对象调用frreeMemory方法释放内存

3. 垃圾回收

3.1 如何判断对象可以回收

  1. 引用计数法:每次引用值+1,不引用-1,当值为0是,触发垃圾回收(问题:两个对象相互引用时则会失效)
  2. (JAVA)可达性分析算法:扫描出不能被回收的对象(根对象 GC ROOT)没有引用的对象,则会被回收(MAT工具)

3.2 四种引用

  1. 强引用:垃圾回收时不回收,只有GC root对象不引用该对象,就会被回收

引用队列: new ReferenceQueue<>();将引用加入到引用队列时,内存有机会回收引用

  1. 软引用(new SoftReference<>();):内存不足时,没有强引用时,会触发垃圾回收,可以配合引用队列来释放软引用自身
  2. 弱引用(new WeakReference<>();):仅有弱引用引用对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列释放弱引用自身
  3. 虚引用(new PhantomReference<>();):必须配合引用队列使用,配合ByteBuffer使用,被引用对象回收时,会将弱引用入队,由Reference Handler 线程调用虚引用方法释放直接内存
  4. 终结器引用(new FinalReference<>();): 无需手动编码,内部配合引用队列使用,在垃圾回收时,终结器引用入队,由Fianalizer线程找到对象并调用fianlize方法,第二次GC时才会被回收

3.3 回收算法

  1. 标记清除:速度较快,会产生内存碎片(不会整理清除后的内存)
  2. 标记整理:速度较慢,没有内存碎片(整理清除后的内存空间)
  3. 复制:没有内存碎片,需要占双倍内存空间(拷贝一份相同的内存空间,将不被清除的放入)

3.4 分代回收

  1. 新生代:空间不足时触发minor gc ,伊甸园和from存活的对象使用copy到to中,存活的对象年龄+1并交换from to
    minor gc 会引发stop the world ,暂停其他用户线程,等待垃圾回收结束后,用户线程恢复
    当对象寿命超出阈值时,会晋升到老年代,最大寿命是15
  2. 老年代:空间不足时触发minor gc ,如果之后空间仍不足,那么触发full gc ,STW的时间更长
  3. 大对象:新生代空间不足时,直接放入老年代,老年代也放不下,就会造成堆内存溢出
  4. 相关虚拟机参数:
    在这里插入图片描述

3.5 垃圾回收器

  1. 串行:单线程,对内存较小,适合个人电脑
  2. 吞吐量优先:多线程,堆内存较大的场景,多核CPU,让单位时间内STW时间最短
  3. 响应时间优先:多线程,堆内存较大的场景,多核CPU,竟可能让单次STW时间变短
  4. G1
    同时注重吞吐量和低延迟,默认暂停200ms
    超大堆内存,会将堆内存划分为多个 大小相等的region
    整体上是标记+整理算法,两个区域之间是复制算法
    1.8使用(-XX:+UseG1GC)开启G1垃圾回收器,jdk9以上默认

3.6 垃圾回收调优

。。。。。。。。。。。。。。。。。。。。。

4 类加载

在这里插入图片描述

4.1 多态的原理

当执行 invokeirtual 指令时,

  1. 先通过栈帧中的对象引用找到对象
  2. 分析对象头,找到对象头中的实际class
  3. class中有(虚方法表)vtable,他在类加载的链接阶段就已经根据方法的重写规则生成好了
  4. 查表得到方法的具体地址
  5. 执行方法的字节码

4.2 异常(Exception table)

4.3.1 类加载(加载)

4.3.2 类加载(链接)

  1. 验证
  2. 准备
  3. 解析

4.3.3 类加载(初始化)

4.4 类加载器

![在这里插入图片描述](https://img-blog.csdnimg.cn/8c24516132374027bfd99b54427b43aa.png

依次向上询问是否已经加载-----这种jvm行为叫做双亲委派

4.5 运行期优化

逃逸分析
– 运行时jvm检查循环创建对象是,该对象是否再循环体外被引用或者逃出循环体内,则会进行优化
方法内联
– 如果发现热点方法,并且长度不太长,会进行内联,所谓的内联就是把方法中的代码复制、粘贴到调用者的位置
字段优化
反射优化

5 内存模型

5.1 JMM

简单地说,JMM定义了一套在多线程读写共享数据时(成员变量、数组)时,对数据的可见性,有序性和原子性的规则保障

  1. 原子性

使用synchronized关键字避免数据因为多线程出现数据不一致

  1. 可见性

volatile(易变关键字)
– 用来修饰成员变量和静态成员变量,可以避免线程从自己的工作线程中查找变量的值,必须到主存中获取他的值,线程操作volatile变量都是直接操作主存
可以用在一个人写,多个人读的情况下,不可以保证原子性

  1. 有序性

volatile 关键字修饰,禁止指令重排序

;