JVM虚拟机
2021-03-20 10:56:11 11 举报
AI智能生成
请大家不要直接克隆,着手梳理一遍才会变成自己的知识
作者其他创作
大纲/内容
一、内存区域
运行内存区域模型
线程共享区域
方法区
⭐方法区是逻辑规范,永久代是方法区的具体实现
非堆,1.7及之前称为永久代、1.8从堆中抽离到内存称为元空间
为什么抽离到内存?
1、为了防止OOM异常
2、为了更好的合并HotSpot和JRockit
⭐只有类模板信息抽离到了内存,字符串常量池还在堆中
堆
OOM:OutOfMemery
先通过jvm调优扩大堆内存
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
堆最小内存1024MB,堆最大内存1024MB,打印GC信息
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
堆最小内存1024MB,堆最大内存1024MB,打印GC信息
扩大内存还未解决问题就该看代码逻辑是否出错了
布局
新生代
Eden
Survivor from
Survivor to
老年代
新生代进入老年代默认需要经历15次轻GC
可以通过参数调节:-XX:MaxTenuringThreshold=20
可以通过参数调节:-XX:MaxTenuringThreshold=20
永久代
方法区的具体实现方式
线程独享
虚拟机栈
栈帧结构
局部变量表
以变量槽为单位,4字节,占32位
⭐Slot变量槽可以复用
⭐Slot变量槽可以复用
存放8大基本数据类型,reference类型引用(对象的引用地址)
操作栈
方法执行时入栈出栈的各种字节码指令
动态连接
把符号引用转为直接引用
返回地址
异常,未在此处处理
return;
附加信息
本地方法栈
PC程序计数器
分配空间方法
指针碰撞
TLAB(Thread Local Allocation Buffer---线程本地分配缓冲区)
空闲列表
对象的内存布局
对象头
markword
指向类信息的指针
数组长度(只有数组对象才有)
实例数据
对齐填充
对象的访问定位
直接指针
好处是比句柄访问少一次指针定位,速度快一倍
句柄访问
在堆中有一个句柄池专门存放句柄,好处是实例数据改变时只改变实例数据,不需要对对象引用修改
二、垃圾收集器
对象存活判断方法
引用计数法
很难解决对象之间的相互引用问题
可达性分析
GC Roots
垃圾回收算法
复制
Eden:Survivor From:Survivor To = 8:1:1
优点
没有内存碎片
缺点
浪费空间
使用场景:对象存活度低
一旦大量对象存活,1/10的Survivor To内存就会不够
一旦大量对象存活,1/10的Survivor To内存就会不够
标记-清除
优点
空间利用率高,可达100%
缺点
两次扫描,浪费时间
容易产生空间碎片化
标记-整理
实现方式
将存活对象向内存空间的一段进行移动
优点
解决了标记-清除算法空间碎片化问题
缺点
移动对象消耗资源较大
是否可以再次优化?
可以执行5次标记-清除算法再执行标记-整理算法
既可以减少移动对象的资源开销,又可以解决空间碎片化问题
既可以减少移动对象的资源开销,又可以解决空间碎片化问题
分代收集理论
针对以上算法的优化
新生代存活率低,使用复制算法
老年代存活率高,使用标记-清除/标记-整理算法
垃圾收集器
新生代垃圾收集器
Serial
单线程
复制算法
Stop The World
-XX:+UseSerialGC(显示调用该垃圾收集器)
ParNew
Serial的多线程版本
对CPU依赖高
-XX:+UseParNewGC(显示调用该垃圾收集器)
-XX:+UseConcMarkSweepGC(调用CMS垃圾收集器后默认在新生代使用该垃圾收集器)
Parallel Scavenge
侧重点在吞吐量
自适应调节策略
-XX:+UseParaIIeIGC或-XX:+UseParaIIeIOldGC(可互相激活)
-XX:ParaIIeIGCThreads=数字N,表示启动多少个GC线程
-XX:+UseAdaptiveSizePolicy(开启自适应策略)
老年代垃圾收集器
Serial Old
标记-整理算法
CMS收集器发生失败时的后备预案
ParNew Old
标记-清除算法
注重吞吐量
CPU资源比较稀缺的情况
⭐CMS
ConcurrentMarkSweep(并发标记清除)
标记-清除算法
标记清除算法会产生内存碎片,只能够选择空闲列表执行内存分配
为什么不采用标记整理算法?
因为并发清除时,如果用压缩整理内存,原来的用户线程使用的内存就无法使用了。
标记压缩更适合STW场景下使用
标记压缩更适合STW场景下使用
4个步骤
初始标记(Stop The World)
并发标记
重新标记(Stop The World)
并发清除
优点
最短停顿时间
并发执行,低停顿
缺点
对CPU要求比较高
在并发阶段会占用一部分线程导致应用程序变慢
无法处理浮动垃圾
并发标记阶段是与工作线程同时运行,如果并发阶段产生垃圾对象,CMS无法进行标记
导致新产生的垃圾对象没有被及时回收,只能在下一次执行GC时释放空间
导致新产生的垃圾对象没有被及时回收,只能在下一次执行GC时释放空间
大量的内存碎片
只能选择空闲列表执行内存分配
调优参数
-XX:+UseConcMarkSreepGC(使用ParNew(Young区用)+CMS(Old区用)+SerialOld的收集器组合,SerialOld将作为CMS出错的后备收集器)
-XX:MSFuIIGCsBeForeCompaction (默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的FullGC,此时不能并发执行,会STW
-XX:CMSlnitiatingOccupanyFraction(设置堆内存使用率的阈值)一旦达到该阈值,则开始进行回收
jdk5前是68%
jdk6及以上是92%
跨代垃圾收集器
⭐G1(Garbage First)
G1是一个并行回收器,也是分代理论,但不再是新生代等等,而是把内存化整为零,重命名为Region
G1跟踪各个region里面垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
4个步骤
初始标记(Stop The World)
并发标记
最终标记(Stop The World)
筛选回收(Stop The World)
优点
并行与并发
并行性: G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW。
并发性: G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况。
兼顾新生代和老年代
具有空间整合能力
region之间用复制算法,整体可以看做是标记压缩算法。
可预测的停顿时间模型
可指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不能超过N毫秒
缺点
小内存应用CMS表现大概率优于G1,在大内存上G1优势发挥更多,平衡点再6-8GB
调优
调优参数
-XX:+UseG1GC(显示开启G1)
-XX:G1HeapRegionSize(设置region区大小,值是2的幂,范围是1MB到32MB之间)
-XX:ConcGCThreads(并发GC线程数量)
-XX:MaxGCPauseMillis(设置期望达到的最大GC停顿时间指标,JVM尽力但不保证,默认200ms)
调优思路
第一步,开启G1垃圾收集器
第二步,设置堆的最大内存
第三步,设置最大的停顿时间
region
所有region大小相同,且在JVM生命周期内不会改变
region可以充当多个角色,但是同一时刻只能是一种身份
G1新增了一个新的内存空间区域,叫做Humongous
主要存放大对象,超过1.5个region的对象放在Humongous里
主要存放大对象,超过1.5个region的对象放在Humongous里
引用
强、软、弱、虚
内存分配和回收策略
内存分配
对象优先在Eden区分配
大对象直接进入老年代
长期存活的对象进入老年代
空间分配担保
回收策略
MinorGC(YoungGC)
当年轻代(Eden区)满时就会触发 Minor GC
MajorGC(OldGC)
当老年代满时会触发MajorGC,只有CMS收集器会有单独收集老年代的行为
FullGC
调用System.gc时,系统建议执行Full GC,但是不一定会执行
老年代空间不足
通过 Minor GC 后进入老年代的空间大于老年代的可用内存
三、类加载机制
类加载器
启动类加载器BootstrapClassLoader
基于C/C++实现
不存在jvm体系,无法获得该ClassLoader
根类加载器,负责加载最核心的Java类,如String、Object、System等
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class
扩展类加载器ExtensionClassloader
平台类加载器PlatformClassLoader
平台类加载器PlatformClassLoader
lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。
由Java语言实现,父类加载器为null
由Java语言实现,父类加载器为null
应用类加载器ApplicationClassLoader
用户自定义的classpath下的类
用户自定义的类加载器UserClassLoader
类的加载过程
加载
ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象
连接
验证
确保class文件的字节流中包含信息符合当前虚拟机要求
文件格式的验证
元数据的验证
字节码验证
符号引用验证
准备
为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值
解析
把常量池中的符号引用替换成直接引用
初始化
如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量
对象的创建过程
new 类名
根据类名在常量池中定位类的符号引用
如果没有找到符号引用,说明该类还未被加载,则进行类的加载、解析和初始化
虚拟机为对象分配内存(位于堆中)
将分配的内存初始化为零值(不包括对象头)
调用对象的<init>方法
双亲委派模型
避免类的重复加载
实例:Tomcat
破坏性双亲委派模型
线程上下文类加载器
实例:JDBC
四、编译器优化技术
方法内联
正常调用方法会出现压栈和出栈操作,为了节省这部分的时间开销,方法内联选择把函数名用函数体替换,用空间换时间
⭐逃逸分析
逃逸程度
方法逃逸
线程逃逸
解决思路
栈上分配
标量替换
同步消除
逃逸分析参数开关(从jdk 1.7开始已经默认开启逃逸分析)
开启逃逸分析:-XX:+DoEscapeAnalysis
关闭逃逸分析:-XX:-DoEscapeAnalysis
逃逸分析并不成熟
根本原因就是无法保证逃逸分析的性能消耗一定能高于本身的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是逃逸的。那这个逃逸分析的过程就白白浪费掉了。
公共子表达式消除
数组边界检查
0 条评论
下一页