JVM虚拟机
2021-03-20 10:56:11 11 举报
AI智能生成
登录查看完整内容
请大家不要直接克隆,着手梳理一遍才会变成自己的知识
作者其他创作
大纲/内容
JVM虚拟机
一、内存区域
运行内存区域模型
线程共享区域
方法区
⭐方法区是逻辑规范,永久代是方法区的具体实现
非堆,1.7及之前称为永久代、1.8从堆中抽离到内存称为元空间
为什么抽离到内存?
1、为了防止OOM异常
2、为了更好的合并HotSpot和JRockit
⭐只有类模板信息抽离到了内存,字符串常量池还在堆中
堆
OOM:OutOfMemery
先通过jvm调优扩大堆内存-Xms1024m -Xmx1024m -XX:+PrintGCDetails堆最小内存1024MB,堆最大内存1024MB,打印GC信息
扩大内存还未解决问题就该看代码逻辑是否出错了
布局
新生代
Eden
Survivor from
Survivor to
老年代
新生代进入老年代默认需要经历15次轻GC可以通过参数调节:-XX:MaxTenuringThreshold=20
永久代
方法区的具体实现方式
线程独享
虚拟机栈
栈帧结构
局部变量表
以变量槽为单位,4字节,占32位⭐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内存就会不够
标记-清除
空间利用率高,可达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场景下使用
4个步骤
初始标记(Stop The World)
并发标记
重新标记(Stop The World)
并发清除
最短停顿时间
并发执行,低停顿
对CPU要求比较高
在并发阶段会占用一部分线程导致应用程序变慢
无法处理浮动垃圾
并发标记阶段是与工作线程同时运行,如果并发阶段产生垃圾对象,CMS无法进行标记导致新产生的垃圾对象没有被及时回收,只能在下一次执行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
最终标记(Stop The World)
筛选回收(Stop The World)
并行与并发
兼顾新生代和老年代
具有空间整合能力
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里
引用
强、软、弱、虚
内存分配和回收策略
内存分配
对象优先在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
扩展类加载器ExtensionClassloader平台类加载器PlatformClassLoader
lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null
应用类加载器ApplicationClassLoader
用户自定义的classpath下的类
用户自定义的类加载器UserClassLoader
类的加载过程
加载
ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象
连接
验证
确保class文件的字节流中包含信息符合当前虚拟机要求
文件格式的验证
元数据的验证
字节码验证
符号引用验证
准备
为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值
解析
把常量池中的符号引用替换成直接引用
初始化
如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量
对象的创建过程
new 类名
根据类名在常量池中定位类的符号引用
如果没有找到符号引用,说明该类还未被加载,则进行类的加载、解析和初始化
虚拟机为对象分配内存(位于堆中)
将分配的内存初始化为零值(不包括对象头)
调用对象的<init>方法
双亲委派模型
避免类的重复加载
实例:Tomcat
破坏性双亲委派模型
线程上下文类加载器
实例:JDBC
四、编译器优化技术
方法内联
正常调用方法会出现压栈和出栈操作,为了节省这部分的时间开销,方法内联选择把函数名用函数体替换,用空间换时间
⭐逃逸分析
逃逸程度
方法逃逸
线程逃逸
解决思路
栈上分配
标量替换
同步消除
逃逸分析参数开关(从jdk 1.7开始已经默认开启逃逸分析)
开启逃逸分析:-XX:+DoEscapeAnalysis
关闭逃逸分析:-XX:-DoEscapeAnalysis
逃逸分析并不成熟
根本原因就是无法保证逃逸分析的性能消耗一定能高于本身的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是逃逸的。那这个逃逸分析的过程就白白浪费掉了。
公共子表达式消除
数组边界检查
0 条评论
回复 删除
下一页