Java Virtual Machine Java程序的运行环境
JVM组成
-
程序计数器
- 线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
-
Java堆
- 线程共享的区域: 主要用来保存对象实例, 数组等, 当堆中没有内存空间可分配给实例也无法再扩展时, 则抛出OutOfMemoryError异常
- 组成: 年轻代+老年代
- JDK1.7中存在一个永久代, 存储的是类信息, 静态变量, 常量, 编译后的代码
- JDK1.8移除了永久代, 把数据存储到本地内存的元空间中防止内存溢出
-
虚拟机栈
- 每个线程运行时所需要的内存称为虚拟机栈, 先进后出
- 每个线程只有一个活动栈帧, 对应着当前正在执行的那个方法
- 栈内存不是越大越好, 栈帧过大会导致线程数变少
- 栈内存溢出
- 栈帧过多导致栈内存溢出–递归调用
- 栈帧过大
-
运行数据区中的方法区Method Area
- 方法区是各个线程共享的内存区域, 主要存储类的信息, 运行时常量池, 虚拟机启动时创建, 关闭虚拟机时释放
- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace
- 常量池:
- 可以看作一场表, 虚拟机指令根据这张常量表找到要执行的类名, 方法名、参数类型、字面量等信息
- 运行时常量池: 当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
-
直接内存
- 直接内存并不属于JVM的内存结构, 不由JVM进行管理, 时虚拟机的系统内存,
- 常见于NIO操作时, 用于数据缓冲区, 他分配回收成本较高, 但读写性能高
类加载器
- 类加载器
- 用于装载字节码文件(.class文件)–JVM只会运行二进制文件, 类加载器的作用就是将字节码文件加载到JVM中, 从而让Java程序能够启动起来.
- 启动类加载器, 扩展类加载器, 应用类加载器(加载开发者自己编写的Java类), 自定义类加载器( 实现自定义类加载规则)
- 双亲委派模型
- 加载某一个类, 委托上一级的加载器进行加载, 如果上级加载器也有上级, 则会继续向上委托, 如果该类委托上级没有被加载, 自家在其尝试加载该类
- 通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
- 为了安全,保证类库API不会被修改
- 类装载的执行过程
- 加载
- ->验证–格式检查, 语法检查
- ->准备–为类变量分配内存并设置类变量初始值
- ->解析–把类中的符号引用转换为直接引用
- ->初始化–对类的静态变量, 静态代码块执行初始化操作
- ->使用–JVM开始从入口方法执行用户的程序代码
- ->卸载–当用户程序代码执行完毕后, JVM便开始销毁创建的Class对象
垃圾回收
-
对象什么时候可以被垃圾回收
- 引用计数法–引用次数为0时代表这个对象可回收
- 当对象间出现了循环引用的话, 则引用计数法就会失效
- 可达性分析算法–利用可达性分析来探索所有存活的对象
- 引用计数法–引用次数为0时代表这个对象可回收
-
JVM垃圾回收算法
- 标记清除算法
- 标记–可达性分析算法
- 标记就和清除速度较快, 但内存碎片化
- 标记整理算法
- 再标记清除算法前提下, 标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。
- 复制算法
- 将垃圾复制进另一块内存再清理
- 效率较高, 内存无碎片, 内存使用率较低
- 标记清除算法
-
JVM分代回收
- 在java8时,堆被分为了两份:新生代和老年代【1:2】
- 对于新生代,内部又被分为了三个区域。
- 伊甸园区Eden,新生的对象都分配到这里
- 幸存者区survivor(分成from和to)
- Eden区,from区,to区【8:1:1】
- 对于新生代,内部又被分为了三个区域。
- 在java8时,堆被分为了两份:新生代和老年代【1:2】
-
JVM垃圾回收器(老年代垃圾, 新生代垃圾)
- 串行垃圾收集器
- 并行垃圾收集器
- 并发CMS垃圾收集器
- 是一款并发的、使用标记-清除算法的垃圾回收器,
- 该回收器是针对老年代垃圾回收的,是一款以获取最短回收停顿时间为目标的收集器
- G1垃圾收集器–作用在新生代和老年代,JDK9后默认使用G1
-
引用的区别
- 强引用–只要所有 GC Roots 能找到,就不会被回收
- 软引用–需要配合SoftReference使用,当垃圾多次回收,内存依然不够的时候会回收软引用对象
- 弱引用–需要配合WeakReference使用,只要进行了垃圾回收,就会把弱引用对象回收
- 虚引用–必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
JVM实践
-
JVM设置调优
- 参数如何设置
- war包部署在tomcat中设置
- 修改TOMCAT_HOME/bin/catalina.bat文件
- jar包部署在启动参数设置
- java -Xms512m -Xmx1024m -jar xxxx.jar
- war包部署在tomcat中设置
- 调优参数
- 对于JVM调优,主要就是调整年轻代、老年代、元空间的内存空间大小及使用的垃圾回收器类型。
- 设置堆空间大小
- 虚拟机栈的设置–栈帧大小
- 年轻代中Eden区和两个Survivor区的大小比例
- 年轻代晋升老年代阈值设置
- 垃圾回收收集器–吞吐量设置/收集器选择
- 调优工具
- 命令工具
- jps 进程状态信息, jstack 查看java进程内线程的堆栈信息, jmap 查看堆转信息, jhat 堆转储快照分析工具,jstat 堆转储快照分析工具
- 可视化工具
- jsonsole 用于对jvm内存, 线程, 类的健康
- visualVM 能够监控线程, 内存情况
- 命令工具
- 参数如何设置
-
Java内存泄露排查思路
- 虚拟机栈–StackOverFlowError
- 方法区–OutOfMemoryError:MetaSpace
- 堆–OutOfMemoryError:Java heap space
- 内存泄漏通常是指堆内存,通常是指一些大对象不被回收的情况
- 通过jmap(程序运行时)或设置jvm参数获取堆内存快照dump
- 通过工具VisuakVM分析dump文件(VisualVM能够分析离线dump文件)
- 通过查看对信息的情况, 定位内存溢出的代码
- 找到对应代码进行修复
-
CPU飙高排查方案与思路
- 使用top命令查看占用cpu的情况
- 查案看哪个进程占用cpu较高
- 使用ps命令查看进程中的线程信息
看对信息的情况, 定位内存溢出的代码- 找到对应代码进行修复
-
CPU飙高排查方案与思路
- 使用top命令查看占用cpu的情况
- 查案看哪个进程占用cpu较高
- 使用ps命令查看进程中的线程信息
- 使用jstack命令查看进程中哪些线程出现了问题, 最终定位问题