文章目录
JVM虚拟机内容
1.什么是jvm (jvm就是一套规范)
定义:jvm就是java程序的运行环境(二进制字节码的运行环境)
好处:1. 一次编写,到处使用(跨平台)
2. 内存自动管理,垃圾回收功能
3. 数组下标越界检查(防止越界覆盖其他代码的内存空间)
4. 多态
比较: JDK(jvm+基础类库+编译工具)-----> JRE(jvm+基础类库)---------->jvm
2. jvm的内存结构
2.1 程序计数器
-
java源代码 — jvm指令 — 解释器(程序计数器) ----机器码 — CPU
-
程序计数器会记录下一条jvm指令的地址,每次执行,解释器都会去程序计数器中获取下一条的指令地址(CPU中的寄存器作为程序计数器)
-
特点:
计数器是线程私有的,属于单独线程的,每个线程都有自己的程序计数器
程序计数器不会存在内存溢出的情况
2.2 栈
- 线程运行需要的内存空间就是栈
- 每个方法运行时需要的内存叫做栈帧
- 每个栈有多个栈帧组成,对应着每次方法调用时所有的内存
- 每个线程中只有一个活动栈帧,对应的就是正在执行的那个方法
- 特点: 先进后出
- 垃圾回收不会涉及到栈内存(垃圾回收是回收堆内存,而栈内存中保持先进后出的特点,每个方法执行完,随之产生的栈帧也会走出栈内存,所以不会有垃圾回收)
- 栈内存分配的越大,可能程序多线程数就会越小,物理内存/栈内存=线程数
- 方法内的局部变量不一定是线程安全的
(1. 如果方法内局部变量没有逃离方法的作用访问,他就是线程安全的 )
( 2. 如果局部变量引用了对象,并且逃离了方法的作用方法,那他就是线程不安全的) - 栈内存溢出 java.lang.StackOverflowError
(1.栈帧过多导致内存溢出 2.栈帧过大导致内存溢出) - 查看cpu占用过高
用top定位到哪个进程对cpu占用过高
ps H -eo pid,tid,%cpu | grep 进程id(定位哪个线程引起的cpu占用过高)
jstack 进程id(可以根据线程id找到问题,进一步定位到源代码行号)
2.3 本地方法栈
- 给本地方法运行提供内存
2.4 堆
- 通过new创建的对象都会使用堆内存
- 特点:
他是线程共享,堆中对象都要考虑线程安全
堆是有垃圾回收机制的 - 堆内存溢出问题:java.lang.OutOfMemoryError
程序中不停的循环new一个对象就会导致堆内存溢出
使用 jvitsalvm工具排查
使用 jconsole 工具排查
2.5 方法区
- 方法区就是个概念,虚拟机启动时创建,存储类的数据
- 1.6之前用的永久代,1.8以后用的元空间
2.6 常量池
- 常量池就是一张表,虚拟机指令根据这张表找到要执行的类名,方法名,参数类型等信息
- 运行时常量池,当类被加载时,常量池信息机会放到运行时常量池,并把符号地址换成真实地址
2.7 SpringTable
- 长度固定,不可扩容,串池中的数据是唯一不重复的
- 常量池中的字符串仅是符号,第一次用到才变为对象
- 字符串变量拼接原理是StringBuilder,常量拼接原理是编译期优化
- 可以使用intern方法,主动将串池中没有的字符串对象放入串池
1.8将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回 - 1.6中stringtable在常量池中,使用永久代空间,1.8中stringtable在堆内存中,使用堆内存的垃圾回收机制,减少内存的压力
- 性能调优:
调整 -XX:StringTableSize=桶个数 适当增加hasp的长度,减少耗时
考虑是否将字符串对象入池
2.8 直接内存
- 定义:不属于jvm虚拟机内存,而是系统内存。java代码和系统都可以访问到的区域。
用于数据缓冲区,回收成本高,性能快,不会被垃圾回收。 - 释放原理:
底层使用unsafe对象调用frreeMemory方法释放内存
3. 垃圾回收
3.1 如何判断对象可以回收
- 引用计数法:每次引用值+1,不引用-1,当值为0是,触发垃圾回收(问题:两个对象相互引用时则会失效)
- (JAVA)可达性分析算法:扫描出不能被回收的对象(根对象 GC ROOT)没有引用的对象,则会被回收(MAT工具)
3.2 四种引用
- 强引用:垃圾回收时不回收,只有GC root对象不引用该对象,就会被回收
引用队列: new ReferenceQueue<>();将引用加入到引用队列时,内存有机会回收引用
- 软引用(new SoftReference<>();):内存不足时,没有强引用时,会触发垃圾回收,可以配合引用队列来释放软引用自身
- 弱引用(new WeakReference<>();):仅有弱引用引用对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列释放弱引用自身
- 虚引用(new PhantomReference<>();):必须配合引用队列使用,配合ByteBuffer使用,被引用对象回收时,会将弱引用入队,由Reference Handler 线程调用虚引用方法释放直接内存
- 终结器引用(new FinalReference<>();): 无需手动编码,内部配合引用队列使用,在垃圾回收时,终结器引用入队,由Fianalizer线程找到对象并调用fianlize方法,第二次GC时才会被回收
3.3 回收算法
- 标记清除:速度较快,会产生内存碎片(不会整理清除后的内存)
- 标记整理:速度较慢,没有内存碎片(整理清除后的内存空间)
- 复制:没有内存碎片,需要占双倍内存空间(拷贝一份相同的内存空间,将不被清除的放入)
3.4 分代回收
- 新生代:空间不足时触发minor gc ,伊甸园和from存活的对象使用copy到to中,存活的对象年龄+1并交换from to
minor gc 会引发stop the world ,暂停其他用户线程,等待垃圾回收结束后,用户线程恢复
当对象寿命超出阈值时,会晋升到老年代,最大寿命是15 - 老年代:空间不足时触发minor gc ,如果之后空间仍不足,那么触发full gc ,STW的时间更长
- 大对象:新生代空间不足时,直接放入老年代,老年代也放不下,就会造成堆内存溢出
- 相关虚拟机参数:
3.5 垃圾回收器
- 串行:单线程,对内存较小,适合个人电脑
- 吞吐量优先:多线程,堆内存较大的场景,多核CPU,让单位时间内STW时间最短
- 响应时间优先:多线程,堆内存较大的场景,多核CPU,竟可能让单次STW时间变短
- G1
同时注重吞吐量和低延迟,默认暂停200ms
超大堆内存,会将堆内存划分为多个 大小相等的region
整体上是标记+整理算法,两个区域之间是复制算法
1.8使用(-XX:+UseG1GC)开启G1垃圾回收器,jdk9以上默认
3.6 垃圾回收调优
。。。。。。。。。。。。。。。。。。。。。
4 类加载
4.1 多态的原理
当执行 invokeirtual 指令时,
- 先通过栈帧中的对象引用找到对象
- 分析对象头,找到对象头中的实际class
- class中有(虚方法表)vtable,他在类加载的链接阶段就已经根据方法的重写规则生成好了
- 查表得到方法的具体地址
- 执行方法的字节码
4.2 异常(Exception table)
4.3.1 类加载(加载)
4.3.2 类加载(链接)
- 验证
- 准备
- 解析
4.3.3 类加载(初始化)
4.4 类加载器
依次向上询问是否已经加载-----这种jvm行为叫做双亲委派
4.5 运行期优化
逃逸分析
– 运行时jvm检查循环创建对象是,该对象是否再循环体外被引用或者逃出循环体内,则会进行优化
方法内联
– 如果发现热点方法,并且长度不太长,会进行内联,所谓的内联就是把方法中的代码复制、粘贴到调用者的位置
字段优化
反射优化
5 内存模型
5.1 JMM
简单地说,JMM定义了一套在多线程读写共享数据时(成员变量、数组)时,对数据的可见性,有序性和原子性的规则保障
- 原子性
使用synchronized关键字避免数据因为多线程出现数据不一致
- 可见性
volatile(易变关键字)
– 用来修饰成员变量和静态成员变量,可以避免线程从自己的工作线程中查找变量的值,必须到主存中获取他的值,线程操作volatile变量都是直接操作主存
可以用在一个人写,多个人读的情况下,不可以保证原子性
- 有序性
volatile 关键字修饰,禁止指令重排序