目录
2.3 本地方法栈(Native Method Stacks)
1、什么是JVM?
1.1 定义
Java Virtual Machine - java程序的运行环境(java二进制字节码的运行环境)
好处:
- 一次编写、到处运行;
- 自动内存管理,垃圾回收功能
- 数组下标越界检查(不会覆盖其他资源的内存)
- 多态
1.2 比较JVM JRE JDK
1.3 JVM体系
2、JVM内存结构
2.1 程序计数器(PC寄存器)
作用:记住下一条jvm指令的地址;(在物理上通过寄存器实现)
特点:
- 线程私有(每个线程有自己的程序计数器)
- 不会存在内存溢出
2.2 栈
2.2.1 定义
Java Virtual Machine Stack(Java虚拟机栈)
- 虚拟机栈:每个线程运行需要的内存空间
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占的内润
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
2.2.2 相关问题
- 垃圾回收是否涉及栈内存?
不涉及,栈内存中的栈帧在方法执行完后会自动弹出栈。
- 栈内存分配越大越好吗?
不是。栈内存越大,会导致线程数减少。默认大小:1024KB
- 方法内的局部变量是否线程安全
如果方法内局部变量没有逃离方法的作用范围,它是线程安全的。
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。
2.2.3 栈内存溢出
报错:java.lang.StackOverflowError
原因:
- 栈帧过多导致栈内存溢出(例如不正确的递归调用、Json解析对象循环引用)
- 栈帧过大导致栈内存溢出
2.2.4线程运行诊断
- 案例一:cpu占用过多
- 定位:top命令查看进程占用cpu情况
- ps H -eo pid,tid,%cpu | grep 进程id (进一步定位哪个线程占用cpu过高)
- jstack 进程id 定位问题代码源码行数
- 案例二:程序运行很长时间没有结果(可能产生死锁)
- jstack命令,Found one Java-level deadlock, 定位问题代码源码行数
2.3 本地方法栈(Native Method Stacks)
作用:给本地方法运行提供内存空间
2.4 堆(Heap)
2.4.1 特点
- 通过new关键字,创建对象都会使用堆内存。
- ‘它是线程共享的,堆中对象都需要考虑线程安全的问题。
- 有垃圾回收机制。
2.4.2 堆内存溢出
报错:java.lang.OutOfMemoryError: Java heap space
原因:正在使用的对象或字符串太多。(jdk1.8后,字符串常量池在堆内存中)
2.4.3 堆内存诊断
工具:
- jps工具:查看当前系统中有哪些java进程
- jmap工具:查看堆内存占用情况,快照信息
- jconsole工具(jdk自带):图形界面,多功能的监测工具,可以连续监测
- jvisualvm工具:可以检查堆中最大的对象。
案例:垃圾回收后,堆内存占用依旧很大。
可以使用jvisualvm工具查看堆内存中的对象。
2.4 方法区(Method Area)
方法区是一种规范,具体物理空间有不同实现
2.4.1 组成
图中忽略其他内容,仅展示方法区和堆的位置
2.4.2 方法区内存溢出(1.8后元空间内存溢出)
报错:java.lang.OutOfMemoryError: Metaspace
原因:生成了太多的类。
场景:
- spring cglib代理,运行期间动态生成类的字节码,运行期间可能生成大量的类
- mybatis cglib代理
2.4.3 运行时常量池
二进制字节码中:类基本信息,常量池,类方法定义,包含了虚拟机指令
反编译HelloWorld.class文件后可以看到:
- 常量池,就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息。
- 运行时常量池,常量池是.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池中,并把里面的符号地址变为真实地址。
2.4.4 StringTable特性
- StringTable:字符串常量池,底层是HashTable结构,不能扩容
- 常量池中的字符串仅是符号,第一次用到时才变为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是StringBuilder(1.8)
- 字符串常量拼接的原理是编译期优化
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
- 1.8将字符串对象尝试放入串池,如果有则不会放入,没有则放入串池
- 1.6将字符串对象尝试放入串池,如果没有则会复制一份放入串池,返回串池中的对象。
- 在内存紧张时,会触发垃圾回收
面试题:
2.4.5 StringTable性能调优
- 可以适当修改虚拟机参数 -XX:StringTableSize (桶个数),原理是增大桶的容量,减小hash冲突,提升效率。
- 考虑将字符串对象是否入池
2.5 类加载器(ClassLoader)
2.5.1 什么是类加载器
作用:JVM只能运行二进制文件,类加载器的作用是将字节码文件加载到JVM中,从而让Java程序能够启动起来。
- 启动类加载器(BootStrap ClassLoader):该类并不继承ClassLoader类,其是由C++编写实现。负责加载Java的核心库(如JAVA_HOME/jre/lib目录下的类库)。
- 扩展类加载器(ExtClassLoader):该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。
- 应用类加载器(AppClassLoader):该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。
- 自定义类加载器:开发者自定义类继承ClassLoader,实现自定义类加载规则。
2.5.2 双亲委派模型
原理:加载某一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类。
原因:
- 可以避免某一个类被重复加载,当父类已经加载后无需重复加载,保证唯一性。
- 为了安全,保证类库API不会被修改
2.5.3 类装载的执行过程
(1)加载:查找和导入class文件
- 通过类的全名,获取类的二进制数据流。
- 解析类的二进制数据流为方法区内的数据结构(Java类模型)
- 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
(2)验证:保证加载类的准确性
- 格式检查:文件格式是否错误、语法是否错误、字节码是否合规
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证:Class文件在其常量池中通过字符串记录自己将要使用的其它类或者方法,检查它们是否存在
(3)准备:为类变量分配内存并设置类变量初始值
- static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成
- static变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成
- static变量是final的引用类型,那么赋值也会在初始化阶段完成
(4)解析:把类中的符号引用转换为直接引用
- 比如:方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法。
(5)初始化:对类的静态变量,静态代码块执行初始化操作
- 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
- 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
(6)使用:JVM 从入口方法开始执行用户的程序代码
- 调用静态类成员信息(比如:静态字段、静态方法)
- 使用new关键字为其创建对象实例
(7)卸载:当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象
- 最后负责运行的 JVM 也退出内存
3.垃圾回收
3.1 如何判断对象可以回收
3.1.1 引用计数法
3.1.2 可达性分析算法(Java虚拟机使用)
- Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
- 扫描堆中的对象,看是否能够沿着GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
- 哪些对象可以作为GC Root对象?虚拟机栈(栈帧的本地变量表)中引用的对象、方法区中类静态属性引用的对象、本地方法栈中JNI (Java Native Interface)引用的对象、活跃线程的引用等。
3.2 四种引用
- 强引用: 只要有 GC Roots 能找到,就不会被回收
- 软引用: 需要配合SoftReference来释放软引用自身,当垃圾多次回收,内存依然不够的时候会回收软引用对象
- 弱引用: 需要配合WeakReference来释放弱引用自身,只要进行了垃圾回收,就会把弱引用对象回收
- 虚引用: 必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
3.3 垃圾回收算法
3.3.1 标记清除 Mark Sweep
- 优点:速度快
- 缺点:容易产生内存碎片
3.3.2 标记整理 Mark Compact
- 优点:不会产生内存碎片
- 缺点:内存拷贝移动,效率低
3.3.3 复制 Copy
标记
复制到To区
删除后交换From和To区
- 优点:不会产生内存碎片
- 缺点:双倍内存空间。
3.4 分代垃圾回收
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发Minor GC,伊甸园和from存货的对象使用copy复制到to中,存货的对象年龄加1,并且交换from to
- minor gc 会引发stop the world,暂停其他用户的线程,等垃圾回收结束后(时间很短),用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
- 如果新生代老年代空间都不足,首先触发minor gc,如果仍旧空间不足,那么触发Full GC,(STW的时间更长)。
3.5 垃圾回收器
3.5.1 串行
- 单线程
- 堆内存较小,适合个人电脑
3.5.2 吞吐量优先
- 多线程
- 堆内存较大,多核cpu
- 让单位时间内,SWT的时间最短( 一小时触发两次:0.2+0.2=0.4)
- -XX:+UseAdpativeSizePolicy 采用自适应大小调整策略,新生代中比例、晋升阈值、整个堆;
- -XX:GCTimeRatio=ratio 默认值19
- -XX:MaxGCPauseMillis=ms 默认值200ms
- -XX:ParallelGCThreads=n 线程数
3.5.3 响应时间优先
- 多线程
- 堆内存较大,多核cpu
- 尽可能让单次的STW时间变短(一小时触发5次,但单次时间短 0.1+0.1+0.1+0.1+0.1=0.5)
3.5.4 G1(JDK9之后的默认垃圾回收器)
- 同时注重吞吐量和低延迟
- 超大堆内存,会将堆划分为多个大小相等的Region,每个区域都可以充当伊甸园、幸存区、老年代。
- 整体上是标记+整理算法,两个区域之间是复制算法
- 分为三个阶段:新生代回收、并发标记、混合收集;三阶段循环
阶段一:
阶段二:
阶段三: