JavaEE:JVM理论摘要

一、JVM内存结构:
1.堆(线程共享):
存放静态变量、字符串常量池,分为新生代(伊甸园Eden+存活区(From survivor+To survivor))、老年代(Tenured)、元空间(本地内存Metaspace)。

2.虚拟机栈(线程隔离):
分为局部变量表、操作数栈(存放临时数据)、指向运行时常量池的引用、方法返回地址、动态链接。

3.本地方法栈(线程隔离):
使用C语言写的native方法

4.程序计数器(线程隔离):
为每个线程分配计数器,用来记录下一步执行的指令

5.方法区(线程共享):
存放静态变量(存堆中)、字符串常量池(存堆中)、类信息(版本/属性/方法/父接口或类/静态常量)(存元空间中)、运行时常量池(编译生成)(存元空间中)

二、类加载过程:
-> java原文件 
-> 编译成class 
-> 加载class:读取类、转换并存到方法区、在堆中产生Class对象
-> 链接:验证文件格式(class以0xCAFEBABE开头/版本号正确)/验证元数据(是否继承父类/是否非法继承final类/非abstract类是否实现所有abstract方法)/验证字节码(运行检查/栈数据类型+操作码操作参数无误/跳转指令指向正确位置)/验证符号引用(常量池中类是否存在/访问的属性及方法是否存在且有权限)、准备(为静态变量分配内存+初始化)、解析(符号引用转为直接引用)
-> 初始化:执行clinit方法(初始化静态变量/静态语句)、创建新对象执行init方法(初始化实例变量/执行构造块/执行构造方法)
-> 使用 
-> 卸载

三、编译器:
1.执行模式(默认混合执行模式,一般使用解释执行,遇到频繁的代码时会转换成编译执行):
解释执行模式:将字节码转换一行执行一行。不用等待编译,总体性能差。
编译执行模式:将字节码转换为机器码,再执行机器码。性能好,但增加开销。

2.查看/设置执行模式:

C:\Users\Administrator>java -version         #mixed mode表示混合执行模式
C:\Users\Administrator>java -Xint -version   #设置为解释执行模式
C:\Users\Administrator>java -Xcomp -version  #设置为编译执行模式,不能编译时会自动用解释执行模式
C:\Users\Administrator>java -Xmixed -version #设置为混合执行模式

3.Hostspot编译器分类:
C1即时编译器:适用客户端局部快速编译
C2即时编译器:适用服务端长期运行的Web程序

4.分层编译级别(别级越高,启动越慢,性能越高/开销越大):
说明:
0层:解释编译
1层(简单C1):简单优化编译代码(不开Profiling)
2层(受限C1):编译带方法调用次数及循环回边执行次数Profiling的部分代码
3层(完全C1):编译带有所有Profiling的代码
4层(C2编译):启用耗时的优化编译,会根据性能监测信息进行优化

只启用0层、4层编译,禁用1、2、3层:

-XX:-TieredCompilation

只启到到第n层编译,禁用后面层:

-XX:+TieredCompilation -XX:TieredStopAtLevel=n  #n可设值为0、1、2、3、4

5.Hostspot计数器分类(可识别热点方法):
(1)方法调用计数器:
说明:
统计方法调用次数,未开分层编译时C1编译器默认1.5千次/C2编译器默认1万次(修改次数:

-XX:CompileThreshold=n),开启分层编译时动态调整阀值

流程:
方法入口 -> 未编译时方法调用计数器值+1(默认使用相对次数)/已编译时执行机器码并结束流程 -> 2个计数器值超过阀值/未超阀值时以解释方式执行代码并结束流程 -> 提交编译请求 -> 解释方式执行代码并结束流程

VM options中半闭计数器热度衰减(使用绝对次数):

-XX:-UseCounterDecay

VM options中设置计数器半衰周期(单位为秒):

-XX:CounterHalfLifeTime

(2)回边计数器(触发OnStackReplacement编译):
说明:
统计方法中循环代码执行次数,未开分层编译时C1编译器默认13995次/C2编译器默认10700次(VM options中修改次数:-XX:OnStackReplacePercentage=n),开启分层编译时动态调整阀值

流程:
回边指令(循环回跳) -> 未编译时回边计数器值+1(默认使用相对次数)/已编译时执行机器码并结束流程 -> 2个计数器值超过阀值/未超阀值时跳到倒数第2步流程 -> 提交OSR编译请求 -> 调整计数器值 -> 解释方式执行代码并结束流程

四、方法内联:
1.说明:
编译器会将被调用方法的内部代码拷贝到调用处,避免发生方法调用。

发生条件:
热点方法体<325字节(VM options中修改大小:-XX:FreqInlineSize)时/非热点方法<35字节(修改大小:-XX:MaxInlineSize);
被调用方法只有一种实现代码(如用private/static/final等修饰的方法),public/protected方法用到多态可以有多种实现代码时则不会触发内联,只有一种实现时能触发;

2.内联条件设置(VM options):

-XX:+Printlnlining -XX:+UnlockDiagnosticVMOptions   #打印内联+诊断相关信息
-XX:lnlineSmallCode=n       #默认1000,机器码代销大于此值,不内联
-XX:MaxnlineLevel=n         #默认9,嵌套内联最大深度
-XX:MaxTrivialSize=n        #默认6字节,方法体小于此值,发生内联
-XX:MinlnliningThreshold=n  #默认250,方法被调用次数小于此值,不内联
-XX:LiveNodeCountlnliningCutoff=n #默认40000,最大活动节点上限,C2有效
-XX:lnlineFrequencyCount=n        #默认100,方法被调用次数超过此值,内联
-XX:MaxRecursivelnlineLevel=n     #默认1,递归调用大于此值,不内联
-XX:+lnlireSynchronizedMethods    #默认开启,允许同步方法发生内联

五、逃逸分析/标量替换/栈上分配:
1.逃逸分析(变量超出自身作用域时就是逃逸):
(1)逃逸分类:
全局变量赋值逃逸:方法内创建的对象赋值给静态/全局变量时
方法返回值逃逸:A方法返回的对象赋值给B方法局部变量时
对象引用逃逸:A类对象作为参数传到B类方法中,传入的对象参数只供方法内使用
线程逃逸:可以在多个线程中访问同个类/对象变量时

(2)逃逸级别(状态标记):
全局级逃逸(存放堆中):对象属于方法返回值逃逸或线程逃逸、重写finalize方法
参数级逃逸:属于对象引用逃逸
无逃逸:对象没有逃逸

(3)开启逃逸分析(默认已开启):

-XX:+DoEscapeAnalysis

2.标量替换:
说明:
用逃逸分析确定了X对象只有方法内访问且可分解时,将会创建临时变量存储X对象中的属性值,不会创建X对象本身。

类型:
标量:无法分解的类型,如基础数据类型、对象引用
聚合量:可分解的类型,如String类型(分解为char[]型)

开启标量替换(默认已开启):

-XX:+EliminateAllocations

3.栈上分配:
说明:
用逃逸分析确定了X对象只有方法内访问时,会在栈上分配对象(将不会在堆中分配),出栈时销毁对象。

开启锁消除(默认已开启):

-XX:+EliminateLocks

六、垃圾收集器:
1.策略:
内存不足时提高回收频率、CPU占用率高时降低回收频率

2.需回收的内存(其他区会自动回收):
堆、方法区

3.判断是否垃圾的算法分类:
引用计数算法(X对象没有引用时可回收)、可达性分析算法(以根对象(根对象为虚拟机栈中引用的对象/方法区静态属性引用的对象/方法区常量引用的对象/本地方法栈JNI引用的对象)往下搜索,与根对象没有连接的X对象可回收)

4.对象引用分类:
强引用、软引用(用了SoftReference,内存不足时回收)、弱引用(用了Weakference,回收器运行时回收)、虚引用(用了ReferenceQueue,回收器运行时回收)

5.回收流程:
对象没有引用 -> 重写了finalize方法且JVM未调过/未重写或JVM已调过finalize方法时/回收并结束流程 -> 对象放入F-Queue且创建低优先级线程执行finalize方法/回收并结束流程 -> 从F-Queue移除且不回收对象

6.垃圾回收算法分类:
(1)标记清除算法(有内存碎片影响内存分配效率):
标记待回收对象 -> 清理被标记对象

(2)标记整理算法(无碎片/整理浪费开销):
标记待回收对象 -> 将不回收的对象压缩到内存一端 -> 清理内存一端以外空间

(3)复制算法(无碎片/性能好/浪费内存):
内存分为A/B区且只使用A区 -> 将不回收对象复制到B区后清除A区 -> B作使用区,A作后续回收复制区

(4)分代收集算法(推荐,新生代/老年代使用各自回收算法):
>分区回收:
新生代回收(Minor GC=Young GC,使用复制算法,GC触发条件:Eden区空间不足)(Eden区+From survivor区+To survivor区):
创建对象存放Eden区 -> GC时将不回收对象复制到From survivor <-> GC时将不回收对象复制到To survivor,再次GC时又会将不回收对象复制回From survivor,循环往复。

老年代回收(Major GC=Full GC,使用标记清除+标记整理算法,GC触发条件:Tenured区空间不足、元空间不足、从新生代过来的对象大于Tenured区剩余空间、执行System.gc方法):
新生代回收循环往复15次后将不回收对象复制到Tenured区、新生代区内存满了或者对象字节大于-XX:PretenureSizeThreshold时会存Tenured区、From+To survivor两区相同次数对象字节总和超过此区一半后,其他对象次数大于该相同次数后将被复制到Tenured区

>优化原则:
设置合适大小的From survivor区+To survivor区、GC尽可能在新生代中触发

>配置各区大小:

-XX:NewRatio=n                 #老年代与新生代空间比例
-XX:SurvivorRatio=n            #Eden区与From survivor区+To survivor区空间比例
-XX:PretenureSizeThreshold=n   #对象>n分配在老年代空间,n为0不设置
-Xms128m                       #堆空间最小值
-Xmx2048m                      #堆空间最大值
-Xmn                           #新生代空间大小
-XX:+DisableExplicitGC         #忽略System.gc方法调用
-XX:NewSize=n                  #新生代初始空间容量
-XX:MaxNewSize=n               #新生代最大空间容量

7.垃圾收集器分类:
(1)Serial收集器(新生代使用):
采用复制算法,收集时停顿用户线程,适用Client端程序(默认)与单核主机。

配置命令:

-Xverify:none     #加速启动(关闭校验)
-XX:+UseSerialGC  #启用Serial收集器(默认),Client模式下使用Serial + Serial Old组合

(2)ParNew收集器(新生代使用):
Serial多线程版本,GC单线程,其他一致,与CMS配套使用。

配置命令:

-XX:+UseParNewGC        #启用ParNew收集器,使用ParNew + Serial Old组合
-XX:ParallelGCThreads   #设置GC线程数

(3)Parallel Scavenge收集器(新生代使用):
采用复制算法,GC多线程,吞吐量优先,适用注重吞吐量的主机。

配置命令:

-XX:+UseParallelGC        #启用Parallel Scavenge收集器,Server模式下使用Parallel Scavenge + Serial Old组合
-XX:MaxGCPauseMillis=n    #设置最大停顿时间
-XX:GCTimeRatio=n         #设置吞吐量,n值0-100,收集时间 <= 1/(1+n)
-XX:+UseAdptiveSizePolicy #打开自适应GC策略,不用手动设置新生代空间大小/Eden区与From survivor区+To survivor区空间比例等

(4)Serial Old收集器(老年代使用):
Serial老年代版本,使用标记整理算法,其他同Serial。与Serial/ParNew/Parallel Scavenge配套使用。

(5)Parallel Old收集器(老年代使用):
Parallel老年代版本,使用标记整理算法,其他同Parallel。与Parallel Scavenge配套使用,适用注重吞吐量与CPU资源场景。

配置命令:
-XX:UseParallelOldGC   #启用Parallel Old收集器,使用Parallel Scavenge + Parallel Old组合

(6)CMS收集器(Concurrent Mark Sweep)(老年代使用):
GC与用户线程并发执行,使用标记清除算法,跨Region基于Rememberd Set/CardTable,出现故障时使用Serial Old,适用服务器Web应用(内存<=6G,JDK8)。
CMS流程:
-> 初始标记阶段:暂缓用户线程,标记GC Roots直连对象)
-> 并发标记阶段:标记GC Roots所有关联对象,此步与后面步用户线程都在运行
-> 并发预清理阶段:GC重新标记上一步引用有更新的对象 
-> 并发可中止预清理阶段:同上步(Eden已使用量>CMSScheduleRemarkEdenSizeThreshold(默认2M)才执行) 
-> 重新标记阶段:重新标记发生变动的对象
-> 并发清除阶段:根据标记,清除垃圾对象
-> 并发重置阶段,清空本次GC相关信息

CMS配置命令:

-XX:+UseConcMarkSweepGC                #启用CMS收集器,使用ParNew + CMS组合,CMS出现故障时使用ParNew + Serial Old组合
-XX:+UseCMSCompactAtFullCollection     #默认开启,完成Full GC后
-XX:CMSFullGCsBeforeCompaction=0       #默认0,几次Full GC后后整理内存碎片
-XX+UseCMSInitiatingOccupancyOnly      #默认关闭,关闭时首次使用上面值作为条件,开启时以上面的值作为条件,超过阀值触发GC
-XX:CMSInitiatingOccupancyFraction=68  #默认68%,老年代占比>=此值触发GC
-XX:+CMSParallelRemarkEnabled          #让ParNew收集器在重新标记阶段使用并行执行,减少标记时间
-XX:+CMSScavengeBeforeRemark           #开启后减少CMS重新标记阶段耗时,让CMS在重新标记前执行YGC回收新生代片区的对象
-XX:-CMSPrecleaningEnabled             #默认开启,关闭<并发预清理阶段>这一步
-XX:+CMSPrecleaningEnabled             #默认开启,开启<并发预清理阶段>这一步
-XX:CMSScheduleRemarkEdenSizeThreshold=2M #Eden区已使用内存大于此值时,可进入<并发可中止预清理阶段>这一步
-XX:CMSMaxAbortablePrecleanLoops=0     #默认0,表示<并发可中止预清理阶段>这一步循环次数
-XX:+CMSMaxAbortablePrecleanTime=5000  #默认5000毫秒,表示<并发可中止预清理阶段>这一步最大耗时
-XX:+CMSClassUnloadingEnabled          #默认开启,启用类卸载       
-XX:+ExplicitGClInvokesConcurrent      #开启后,调用System.gc()不会停顿用户线程(相当于执行了CMS GC,而不是Full GC)
-XX:CMSScheduleRemarkEdenPenetration=50   #默认50%,Eden区使用占比阀值,达到此值关闭<并发可中止预清理阶段>这一步

(7)G1收集器(新生代+老年代整个堆使用):
内存分为一块块Region(新生代(Eden/Survivor)、Old老年代、Humongous),使用标记整理算法,跟踪Region内垃圾价值,优先回收价值高的Region,适用服务器Web应用(内存>6G,JDK>=8)。

G1垃圾收集机制:
Young GC:Eden满时触发GC,将Eden中对象移到Survivor,Survivor原有对象移到新Survivor(满足条件的到Old),未使用Region放到空闲列表。
Mixed GC:老年代大小占比超过阀值触发GC,回收新生代(Eden+Survivor)+部分老年代空间,流程:初始标记 -> 并发标记 -> 最终标记 -> 筛选回收(排序/回收(构建Region回收列表,将不回收对象复制到空Region中,删除需回收Region,无内存碎片,暂停用户线程))
Full GC:实例化或复制对象时内存不足触发GC,使用Serial Old,尽可能减少Full GC

G1配置命令:

-XX:+UseG1GC                          #启用G1收集器
-XX:G1HeapRegionSize=n                #每个Region大小,值为1-32MB,不配置时会自动设置大小
-XX:MaxGCPauseMillis=200              #默认200毫秒,最大停顿时间
-XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=5      #默认5,设置年轻代在堆内存中最小占比
-XX:+UnlockExperimentalVMOptions -XX:G1MaxNewSizePercent=60  #默认60,设置年轻代在堆内存中最大占比
-XX:ParallelGCThreads=n               #设置并行阶段的GC线程数,逻辑cpu核数<8时n=逻辑cpu核数,逻辑cpu核数>8时n=逻辑cpu核数*5/8
-XX:ConcGCThreads=n                   #设置并发阶段的GC线程数,n = ParallelGCThreads值 / 4
-XX:InitiatingHeapOccupancyPercent=45  #默认45%,老年代在堆内存中占比,超过时触发Mixed GC
-XX:+UnlockExperimentalVMOptions -XX:G1MixedGCLiveThresholdPercent=85  #默认值85,Region中对象活跃度<此值时,将处于Mixed GC回收周期中
-XX:G1HeapWastePercent=5              #默认5%,允许垃圾对象占用堆内存最大占比
-XX:G1MixedGCCountTarget=8            #默认8次,标记周期结束后最多执行n次Mixed GC
-XX:G1ReservePercent=10               #默认占堆内存10%,设置预留空闲内存占比
-XX:+UnlockExperimentalVMOptions -XX:G1OldCSetRegionThresholdPercent=10 #默认占堆内存10%,设置Mixed GC中被回收的老年代比例上限
-XX:-G1PrintHeapRegions               #默认false,打印Region分配与回收日志
-XX:-G1PrintRegionLivenessInfo        #默认false,打印堆中所有Region活跃度日志

(8)Shenandoah收集器:
只限OpenJDK、类似G1的内存布局与回收策略,标记整理算法并发执行,跨Region基于Connection Matrix,适用低延迟/快响应场景。

开启:-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

流程:初始标记 -> 并发标记 -> 最终标记 -> 并发回收 -> 初始引用更新 -> 并发引用更新 -> 最终引用更新

(9)ZGC收集器:
使用Page内存布局,可动态创建/销毁Page,小Page为2MB存放<256KB对象,中Page为32MB存放>=256且<4MB对象,大Page动态容量存放>=4MB对象,适用低延迟/快响应场景。

开启:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

流程:并发标记 -> 并发预备重分配 -> 并发重分配 -> 并发重映射

(10)Epsilon收集器:
只控制内存分配,不回收垃圾,堆内存耗尽时关闭JVM,适用差异性分析统计延迟、确定内存分配阀值等。

开启:

-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

八、更多配置参数见官方文档:
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

你可能感兴趣的:(JavaEE,java)