- JVM 是什么
1.Java 虚拟机,能够把Class文件编译成不同平台的CPU指令集
2.一次编写,到处使用
- JVM工作原理
2.1类加载过程
类的加载过程分为 加载、链接、初始化、使用、卸载这几个过程;
类的装载由类加载器完成,负责查询和加载class文件
2.1.1加载loading
- 通过类的完全限定名获取类的二进制字节流
- 将二进制字节流代表的静态存储结构转化为方法区运行时的数据接口
- 在内存中生成class对象,这个过程发生在类加载器过程
2.1.2链接Linkling
分为验证、准备、解析三个阶段
验证: 验证class文件是否符合JVM规范。有文件格式验证、元数据验证、字节码验证、 引用符号验证
准备:给类的静态变量分配内存并且初始化默认值。比如0、false等
private static int a =1 ; 初始化默认值为a =0
解析:将类的符号引用转化为直接引用
符号引用:是指用一串符号表示所引用的目标;
直接引用:可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄,有了直接引用代表引用的对象已经存在JVM 内存中
我的理解:比如类A引用了类B 那么在A.class文件有一串符号表示B 那么这串符号就是符号引用,此时B不一定加载到内存中,当加载A 触发了引用B时,此时B就会进行加载,B所在的地方地址是直接引用 已经进入内存了
2.1.3初始化initialize
对类的静态变量以及静态代码块初始化操作。
private static int a =1 ; 初始化为a =1
2.2类加载器
类加载器的作用把claas 文件加载到JVM 内存中并生成相应的class对象,在loading 阶段借助类加载其完成通过类的全限定名称获取二进制字节流
每个java类都有一个引用指向他的类加载器ClassLoad,数组不能通过classLoad 完成,数组没有对用的二进制字节流文件由JVM直接生成
2.2.1类加载器分类
2.2.2双亲委派机制
当一个类加载器收到类加载的请求时,它不会第一时间自己尝试加载这个类,而是把请求委派给自己的父类加载去完成,逐层向上,一般都是启动类加载器优先加载这个类,当父类加载无法实现这个加载时才,才会去找他的子加载器去加载一下向下,直到自己去尝试加载这个类。如果都加载不了就会抛出classNotFound异常;
逐层委派会出现一个加载优先级的层次关系,而且加载一次,比如类java.lang.Object在rt.jar 属于启动类加载器的范畴,这样由bootstrap 加载器加载,不管哪个类加载它请求最后都是模型最顶端的启动类加载器进行加载,不会造成核心库的jar 被覆盖的问题;如果没有双亲委派都自己自行加载这个Oject,会造成程序混乱
2.2.3破坏双亲委派
- 继承ClassLoader 抽象类重写loadClass方法
- 线程上下文加载器java.lang.Thread 设置设置当前类的类加载器的类型setContextClassLoad()方法
- java运行时数据区
3.1方法区
JVM启动被创建,线程共享,
存储已被虚拟机加载的类的数据信息 、常量、静态变量、即时编译器编译后的代码等数据,
运行时常量池:用于存放编译器生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池
分配内存不足时会出现OOM
3.2堆
JVM启动被创建所有线程共享是虚拟机管理内存的最大的一块内存
存放实例化对象和数组
空间不足会导致:OOM
3.2.1Java 内存模型
Java内存模型分为对象头、实例数据和对齐填充
对齐填充的好处:为了提高内存访问效率,减少内存访问时间,并防止内存碎片的产生。虽然填充增加了内存大小时间换空间,64位的CPU一次可以处理8个字节
3.3程序计数器
记录当前线程执行的字节码指令的地址,私有存储,不会OOM,
Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。
3.4虚拟机栈
先进后出 线程私有,生命周期与线程相同
每个方法执行都会产生一个栈帧可以存放局部变量表、操作数栈、动态连接、方法出口、附加信息等,当一个方法执行完后,其对于的栈帧就会从虚拟机栈内弹出
3.5本地方法栈
线程私有,他是虚拟机使用到的本地native 方法 例如hashcode()
本地方法栈会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
- JVM 堆空间结构
Java内存管理主要分为内存的回收和内存的分配,java内存管理内存核心堆内存的对象分配和回收
堆内存的设计分为新生代、老年代和元数据区,主要是基于对象生命周期和垃圾回收效率的考虑;
1)生命周期差异:有的对象生命周期短,很快被回收,而有的生命周期比较长,可能在整个应用程序周期都存在,所以分开管理比较友好
2)垃圾回收算法的优化:针对不同生命周期的对象,可以使用不同的垃圾回收算法以提高效率。新生代中通常使用复制算法,因为这里的对象生命周期短暂,复制算法可以快速回收不再使用的对象。而老年代则使用标记-清除或标记-整理算法,因为较长生命周期的对象不适合复制算法;
3) 性能优化:分代内存管理有助于提高垃圾回收的性能。新生代的垃圾回收发生频率较高,但每次回收的内存量较小;而老年代的垃圾回收发生频率较低,但每次回收的内存量较大。这减少了垃圾回收的停顿时间,提高了应用程序的响应性能。
五、JVM GC
5.1定位垃圾对象
- 引用计数法
对于某个对象创建的时候其引用技术设置为1,当其他对象或者方法引用这个对象的时引用计数就会增加,当这个引用关系取消掉被删除,引用计数就会减少,当引用技术为0的时表示不再被引用可以回收,
缺点是如果存在循环引用时,对象就无法被回收
- 可达性分析
以GC ROOTS为对象作起点通过引用链搜索,在引用链上关联的对象为可达的不会被回收,没在引用链证明此对象是不可达的可以回收
5.2 GC ROOT 的对象
1)虚拟机栈中引用的对象:每个线程在执行方法时,会在虚拟机栈中创建一个栈帧,栈帧中的局部变量引用的对象;
2)方法区中类静态属性引用的对象:类的静态属性引用的对象;
3)方法区中常量引用的对象,常量池中的常量引用的对象,如字符串常量池中的字符串对象
4)本地方法栈中JNI引用的对象:通过本地方法接口(JNI)引用的对象
5.3垃圾回收的算法
5.3.1标记清除算法
产生大量的内存碎片,会导致以后的运行程序分配较大的对象时找不到合适的内存而提前触发下一次gc。标记清除两个过程都比较耗时效率不高
5.3.2标记整理算法
解决了内存碎片的问题, 比标记清楚更耗时
5.3.3复制算法
开始就浪费空间,复制存活的对象到另一边内存中 然后把这块的区域清空,大量复制操作效率更低,空间利率低
5.3.4分代收集
是目前虚拟机常用的算法,将内存分为两块,新生代、年老代根据年代特点进行回收
新生代生命周期短、存活对象少选择标记-复制算法
老年代对象存活周期长 适用于标记清除或者标记整理
5.4垃圾收集器
5.4.1 Serial垃圾收集器
最基本的垃圾收集器、单线程的;当他执行垃回收的时候出现STW(应用程序暂停),直到回收完成,适用于小型应用和单线程场景
可以用于新老年代
新生代采用复制算法
老年代采用标记整理算法
5.4.2 Paraller垃圾收集器
他是Seria的多线程版本,多个线程回收,提高效率,他也会STW
适用于多核处理器,对停顿时间要求不高的场景
可以用于新老年代
新生代采用复制算法
老年代采用标记整理算法
5.4.3 CMS(ConcMarkSweep)
它是并发标记清除,用于老年代 使用的时候需要配合新生代垃圾回收器使用(Parallel)
,特点就是CMS垃圾回收器能够在不影响应用正常运行的情况下
优点:
并发收集地停顿
缺点:
CPU资源敏感
无法处理浮动垃圾
产生大量内存碎片
分为一下几个阶段
初始标记:暂停所有应用线程,标记与GC root直接相连的对象,用时很短
并发标记:初始标记完成应用程序恢复执行,垃圾回收器 就会在后台与应用程序并发执行, 他会从初始标记的标记出的对象开始,递归找可达的对象
重新标记:并发标记阶段是在应用线程运行的同时进行的,并发标记过程中会有一些对象的状态在标记过程中发生变化,停止所有应用程序,对并发阶段标记的对象重新标记状态。重新标记是进行一次全面的快速扫描;
并发删除: 重新标记完成后,应用线程再行执行,垃圾回收器在后台并发执行;扫描整个堆 内存找到没有标记的对象,清除他们释放内存
5.4.4G1(Garbage-First)
Java堆的内存布局与就与其他收集器有很大差别; 将内存区域划分为多个小块区域(Region)默认2048, 保留了新生代和老年代的概念但是不再是隔离状态,每个Region 区域大小是一样的可以是1M到32M之间的数值,但是必须保证是2的n次幂, 如果对象太大会被放在H(humongous)中是指超过区域的50%,
5.4.4.1主要特点
并行并发:利用多核处理器优势,并行执行垃圾回收任务,与应用程序 并发执行减少停顿时间
分区域收集:将内存区域划分为多个大小相等的区域(Region)默认2048保留了新生代和老年代的概念但是不再是隔离状态
可预测的停顿时间:G1 GC通过建立一个可预测的停顿时间模型,允许用户明确指定在一个特定时间片段内,垃圾收集所造成的停顿时间不得超过某个阈值。可以通过设置阈值来限制停顿时间
优先回收垃圾最多区域:G1GC 会通过维护一张优先级列表来跟踪各个Region中的垃圾堆 积情况和回收价值优先回收垃圾最对的区域
标记整理算法:整体采用的标记整理算法,2个region区域之间进行垃圾回收时采用标记清 组合策略能提高内存使用率和垃圾收集效率
5.4.4.2 运行过程
初始标记:暂停所有应用线程、进行初始标记标记出与根对象直接相连的对象
并发标记:初始标记完成应用程序恢复执行,垃圾回收器 就会在后台与应用程序并发执行, 他会从初始标记的标记出的对象开始,递归找可达的对象,
最终标记:并发标记阶段应用线程运行的同时进行的,可能会有一些对象的状态发生变化, 垃圾回收器会再次暂停应用线程,进行最后一次标记
筛选回收:在完成所有标记工作,G1垃圾收集器会进入筛选回收阶段,根据之前的筛选结 果,选择垃圾的最多的区域进行回收,然后它会暂停应用线程,将这些区域中的存活对 象复制到其他区域,并清理掉原区域的所有垃圾。
5.4.4.3跨代引用问题
一个对象引用了另一个区域或代的对象
用了Rest 记忆集来记录这些引用关系,每当一个对象引用了另一个位于不同区域的对象时吗G1 垃圾回收器就会更新Rset便于垃圾回收时快速找到引用
- JVM 参数调优
5.1 常用参数
-Xms -Xmx :设置堆的初始化内存和最大内存合理分配
-Xss: 设置堆栈的大小,保证线程正常使用避免过多浪费资源
-XX:NewRatio:这个参数用于设置新生代和老年代的比例,合理分配新生代和老年代的空间可 以提高垃圾回收效率
-XX:SurvivorRatio:设置新生代eden和survivor 比例
-XX:MaxTenuringThreshold 设置新生代晋升老年代的年龄阈值
5.2 GC 相关
5.2.1垃圾回收器:
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
-XX:+UseSerialGC
-XX:+UseParallelGC
5.2.2 GC相关参数
-XX:+PrintGCDetails 打印详细的垃圾回收日志
-XX:+PrintGCDateStamps:打印详细的垃圾回收日志时间戳,
-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath:当OOM时候这些参数会 自动导出转存储文件分析泄露问题
-XX:G1HeapRegionSize :设置G1 堆内存每块Region区域的大小1-32 2的n次方
-XX:MaxGCPauseMillis:设置G1最大停顿时间默认200ms
-XX:ParallelGCThreads: GC 并行线程数 一般等于CPU核数相等,最大值是8
-XX:ConcGCThreads GC 并发标记线程数并行垃圾回收线程数的4分之一
-XX:InitiatingHeapOccupancyPercent: 设置触发并发GC周期的Java堆占用率阈值,堆内 存的占用率达到这个值时,G1收集器会启动一个并发GC周期。默认值是45%,这 意味着当堆内存的45%被占用时,就会触发垃圾回收
5.3最佳实战
- 不要手动设置新生代和老年代的大小,只要设置整个堆的大小
- 不断调优暂停时间目标 100-200ms
时间太短 就会导致G1跟不上垃圾产生的速度会出现full gc
- 使用-XX:ConcGCThreads=n来增加标记线程的数量
- 适当增加堆内存大小
- JVM 调优工具
Jconsole 查看应用程序的运行概况、内存、线程、类、VM概括、MBean等信息
VisualVM 实时jvm性能监控内存和线程分析等功能
jps :主要用于列出当前系统中所有正在运行的Java进程及其主类名,它对于快速定位需要 调试或监控的Java进程非常有帮助。
- -v:输出JVM启动时显示指定的JVM参数
- -L: 输出进程id 和jar 全路径
- -q 输出进程号
- -m输出JVM启动时传递给main()的参数
Jstat 监控JVM进行垃圾回收(GC)的相关信息
- Jstat -class pid
- Loaded:加载类的数量
- Bytes:加载类的size,单位为Byte
- Unloaded:卸载类的数目
- Bytes:卸载类的size,单位为Byte
- Time:加载与卸载类花费的时间
- Jstat -compiler
- Compiled:编译任务执行数量
- Failed:编译任务执行失败数量
- Invalid:编译任务执行失效数量
- Time:编译任务消耗时间
- FailedType:最后一个编译失败任务的类型
- FailedMethod:最后一个编译失败任务所在的类及方法
- jstat -gc pid JVM
- S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
- S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
- S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
- S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
- EC:年轻代中Eden(伊甸园)的容量 (字节)
- EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
- OC:Old代的容量 (字节)
- OU:Old代目前已使用空间 (字节)
- MC:metaspace(元空间)的容量 (字节)
- MU:metaspace(元空间)目前已使用空间 (字节)
- CCSC:当前压缩类空间的容量 (字节)
- CCSU:当前压缩类空间目前已使用空间 (字节)
- YGC:从应用程序启动到采样时年轻代中gc次数
- YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
- FGC:从应用程序启动到采样时old代(全gc)gc次数
- FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
- GCT:从应用程序启动到采样时gc用的总时间(s)
jinfo 用于虚拟机配置参数
- Jinfo -flags pid 输出进程JVM 参数
- Jinfo -sysprops pid 进程的所有参数
Jmap 用于生成 Java Heap Dump 文件,以及查看 Java 进程中的内存使用情况。
- Jmap -head pid
打印 Java 堆概要信息,包括使用的 GC 算法、堆配置参数和各代中堆内存使用情况
Jstack 可以排查排查死锁
Jstack pid