JVM
-
JVM是Java虚拟机,是一个可以执行Java字节码的虚拟机进程,有跨平台性,一次编译到处运行,自动内存管理,自动GC
-
类加载器:加载字节码文件(.class文件)
-
运行时数据区:存放字节码文件,并分配内存
- 方法区
- 虚拟机栈
- 本地方法栈:栈是线程独有的,线程安全的,存放局部变量和对象的引用,随线程开始而加载,线程结束释放,回收快
- 堆:是共享内存,线程不安全,主要存放各种类的实例对象和数组,JVM启动时创建,回收慢,不定时回收
- 程序计数器
-
执行引擎:把内存里的字节码文件翻译成底层系统指令,交给cpu执行
-
本地库接口:这个过程需要调用其他语言的本地库接口(Start()方法就是Natvie Method)
-
ClassLoader类加载过程
- 加载一个class文件
- 验证这个class文件的正确性
- 准备为类的静态变量分配内存
- 解析将常量池内的符号引用替换为直接引用的过程
- 执行初始化方法
<clinit> ()方法的过程 - 卸载,用完之后被回收
-
双亲委派模型
JVM提供了三层的ClassLoader,从上到下分别是BootStrap(加载核心类库),Ext(加载扩展Jar),App(加载主函数)
当类加载器收到加载请求的时候,首先会去判断这个class是否已经被加载过,如果没有就递归调用loadClass方法去委托给父类,如果父类为null,则调用顶层父类BootStrap去加载,如果还加载不到,会依次下沉到子加载器,一直到最底层,如果还没有,抛出ClassNotFoundException
避免类的重复加载,(父类已经加载过,子类就没必要再加载一次)
保护程序安全,防止核心API被随意篡改
-
定位垃圾:
- 垃圾:没有任何引用指向的一个对象或者多个对象(循环引用),就是栈中没引用,堆里有对象
- 引用计数:不能解决循环引用问题
- 根可达算法:JVM判断一个对象是否可以回收就是用的根可达算法,GC Root作为算法的起点,通过这个起点可以遍历所有活着的对象,剩下的就是可以回收的。栈里的变量,Class(方法区的Class对象)中的静态变量,常量池都可以作为GC Root,特点就是当前时间存活的对象。GC主要回收堆和方法区中的对象
- 标记清除 :先标记再清除,位置不连续,会产生碎片
- 复制算法:内存一份为二,一块触发GC,把存活的对象复制到另一块,之前一块直接释放。速度快,但浪费空间
- 标记整理:标记清除的时候,进行整理,没有碎片,效率偏低
-
JVM内存分代模型
-
新生代 = Eden + 2个survivor区 默认比例: 8:1:1
-
老年代 有标记清除(CMS),标记整理(SerialOld),FullGC就是标记整理,默认GC15次,进老年代
-
区别
- 永久代PermGen(1.7)和元空间Metaspace(1.8)都是方法区的实现,都是用来放Class对象的
- 永久代用的是JVM内存,大小指定比较困难,容易造成溢出
- 元空间是本地内存,受限于物理内存
- 字符串常量1.7-永久代,1.8是堆
-
-
GC Tuning
- 减少GC的频率和FGC的次数
- 一是调参,二是改程序
-
区别
- 并发:指的是多个任务,在同一时间段内同时发生了,交替执行的,抢占资源的
- 并行:指的是多个任务,在同一时间点上同时发生了,多个任务之间不会抢占CPU资源
- 只有在多CPU或者多核的情况中,才会发生并行,否则都是并发,只是CPU切换时间片太快,看着像同时发生
-
常见的垃圾回收器
-
有串行的Serial和SerialOld,会有较长的STW(Stop the Word暂停其他所有的工作线程)停顿时间,对用户程序影响很大
-
并行的PS和PO,这是JDK1.8默认的垃圾收集器,适合吞吐量优先的应用
-
并行的还有ParNew,这是Serial的多线程版本,复制算法,年轻代垃圾收集器都是复制算法
-
ParNew一般配合CMS来做垃圾回收,CMS ConcurrentMarkSweep 并发标记清理,老年代垃圾回收器,是一种牺牲吞吐量来减少垃圾回收停顿时间的垃圾回收器,并发操作,低延迟,注重的是响应时间,对CPU资源敏感,初始标记和重新标记过程会独占CPU,会有STW,重新标记过程主要处理漏标(本来不可达,但是用户程序运行过程中把他改成可达了)的对象,使用的是三色标记算法的增量更新,无法处理浮动垃圾(本来可达的对象,因为用户程序并发进行,又变的不可达了,只能在下次GC清理),因为位置不连续,所以并发清理完成后会产生内存碎片,如果碎片太多,触发FGC,会切换到SerialOld,性能会降低。
-
上面所说的都是物理分代模型,最后是G1:面向服务端应用的垃圾回收器,逻辑分代,物理不分代,他把整个堆分成了大小相同的Region,从逻辑上区分了年轻代和老年代,能够充分的利用多CPU,多核环境下的硬件优势,去缩短STW停顿时间,和CMS标记清理算法不同,G1整体上是标记整理算法,局部上(两个Region之间)是复制算法,这两种算法都意味着不会产生内存碎片,G1添加了预测机制,他STW更可控,他通过维护RSet去记录了对象分区之间的引用,避免全量扫堆,后台还维护了一个优先列表CSet(回收集合),会优先回收那些价值最大的Region(回收所获得的空间大小以及回收所需要时间的经验值),这也是Garbage-First名称的由来。
-
CMS和G1垃圾回收过程:响应优先选择:G1或者CMS
-
初始标记:只标记GCROOT可以直接关联的对象,速度快,独占CPU,会有STW
-
并发标记:标记所有可达对象
-
重新标记:标记漏标的对象,独占CPU,会有STW(用户线程是并发运行的,对象引用可能会发生变化)
三色标记算法:
-
黑:本对象访问过,本对象引用到的其他对象也全部访问过
-
灰:中间态,本对象访问过,本对象引用到的其他对象没有全部访问完
-
白:没有访问过
-
标记完后,只有白色是可以回收的
- CMS是增量更新:有新的引用进来的时候,记录下来,再等待遍历
- G1是原始快照:在原来的对象引用发生变化之前,把原来的对象记录下来
-
-
CMS并发清理: 基于标记结果,直接清理对象
-
G1清理:根据优先列表,优先去处理那些价值最大的Region
-
-
JVM参数分类
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
-XX:+PrintCommandLineFlags 查看默认JVM参数
-XX:+PrintGCApplicationStoppedTime 打印JVM停顿时间
-XX:MaxTenuringThreshold 升代年龄,最大值15
-
JDK命令行
-
jps:查看当前所有的java进程
-
jinfo:查看配置参数,jinfo pid 常看当前进程的信息
-
jstat:查看运行状态,jstat -gc pid 查看gc的大小和使用情况
-
jmap:查看堆的信息
- -dump:生成堆转储文件
- -heap:显示堆的详细信息,哪种收集器,分代情况
- -histo:对象统计信息
-
jstack:查看栈的信息
-
top:系统运行状态和cpu的使用率,可配合jstack查看哪个线程有问题,不过一个是10进制,一个是16进制
-
-
G1常用命令
- -XX:+UseG1GC 使用G1
- -XX:MaxGCPauseMillis:暂停时间,默认值200ms。G1会尽量达成,如果达不成,会逐渐做自我调整
- -XX:G1HeapRegionSize:Region大小,默认最多生成2048块,每块的大小需要为2的幂次方,最大值为32M,Region的大小主要是关系到Humongous Object的判定,当一个对象超过Region大小的一半时,则为巨型对象,那么其会至少独占一个Region,如果一个放不下,会占用连续的多个Region
-
-
Arthas 阿里开源的Java诊断工具,在线排查问题,实时监控JVM状态
- dashboard:监控面板,显示线程,堆,GC的所有信息
- thread:查看线程信息 thread -b 打印死锁
- jvm:查看jvm信息
- trace:方法调用耗时
- jad:在线反编译class文件字节码 ognl 修改线上代码
- heapdump : 生成堆转储文件
-
线上环境8核,32G
-XX:+UseConcMarkSweepGC 使用CMS
Xms Xmx 最大最小堆 16G 设置相同值,防止内存抖动(每次GC完重新分配内存)
-XX:NewSize -XX:MaxNewSize 新生代初始分配和最大分配内存 10G
-XX:ParallelGCThreads GC线程数 8
-XX:CMSInitiatingOccupancyFraction 默认为68,即当老年代的空间使用率达到68%时,会执行一次CMS回收,根据应用内存增长的特点,来调优;缓慢,则可以设置一个稍大的值,大的阈值可以降低CMS的触发频率,减少老年代回收的次数,如果增长快,就降低这个值,设置成75%
-XX:MaxTenuringThreshold 生代年龄 12