文章目录
回收算法
标记清除
标记:
找出内中需要回收的对象,并且把他们标记出来
清除:
清除掉被标记的对象,释放出他们的内存
缺点:
标记和清除两个过程比较耗时,效率不高
会产生大量内存碎片(剩余的内存不连续),碎片太多导致以后分配较大对象时无法找到足够的连续内存而不得不触发垃圾回收
两次扫描
用在old区回收:适用于存活对象多的场景
清除后分配规则
1、首次适应算法:
首次适应算法(Fisrt-fit)就是在遍历空闲链表的时候,一旦发现有大小等于需要的大小之后,就立即把该块分配给对
象,并立即返回
2、最佳适应算法:就是在遍历空闲链表的时候,返回刚好等于需要大小的块
3、最差适应算法:就是在遍历空闲链表的时候,找出空闲链表中最大的分块,将其分割给申请的对象,其目的就是使得
分割后分块的最大化,以便下次好分配,不过这种分配算法很容易产生很多很小的分块,这些分块也不能被使用
复制
将内存划为两块相同的部分,每次只是用其中一块;当其中一块内存使用完,将已存活的对象复制到另一块区域中,
(移动过后使得内存连续,避免内存连续)再将使用过的那一块内存空间清除掉
缺点:空间利用率低
young区中的两个s区就是用的复制算法(old区对象存活时间长,没必要赋值来复制去),适用于存活对象少的场景
标记整理
就是在标记清除的基础上再进行整理,避免内存碎片
两次扫描,移动指针;效率低
用在old区回收
标记清楚
首次适应算法(Fisrt-fit)就是在遍历空闲链表的时候,一旦发现有大小等于需要的大小之后,就立即把该块分配给对象,并立即返回。
最佳适应算法(Best-fit)就是在遍历空闲链表的时候,返回刚好等于需要大小的块。
最差适应算法:(Worst-fit)就是在遍历空闲链表的时候,找出空闲链表中最大的分块,将其分割给申请的对象,其目的就是使得分割后分块的最大化,以便下次好分配,不过这种分配算法很容易产生很多很小的分块,这些分块也不能被使用
整理算法分类
1、随机整理:对象的移动方式和它们初始的对象排列及引用关系无关
双指针回收算法:实现简单且速度快,但会打乱对象的原有布局
2、线性顺序:将具有关联关系的对象排列在一起
3、滑动顺序:将对象“滑动”到堆的一端,从而“挤出”垃圾,可以保持对象在堆中原有的顺序
Lisp2算法(滑动整理算法):需要在对象头用一个额外的槽来保存迁移完的地址
单次遍历算法:滑动回收,实时计算出对象的转发地址而不需要额外的开销
引线整理算法:可以在不引入额外空间开销的情况下实现滑动整理,但需要2次遍历堆,且遍历成本较高
jvm参数
标准参数
每个JDK版本都不会变的
java -xxx
-version
-help
-server
-cp
-X参数
非标准参数,也就是在JDK各个版本中可能会变动
java -xx
-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式,JVM自己来决定
-XX参数
非标准化参数,相对不稳定,主要用于JVM调优和Debug
格式1、
-XX:[+-]<name>: +或-表示启用或者禁用name属性;例如:-XX:+UseCompressedClassPointers(开启指针压缩)
格式2、
-XX<name>=<value> 表示name属性的值是value;例如:-XX:InitialHeapSize=258496576(初始化堆大小)
缩写:
-Xms1000M等价于-XX:InitialHeapSize=1000M
-Xmx1000M等价于-XX:MaxHeapSize=1000M
-Xss100等价于-XX:ThreadStackSize=100
查看所有参数:java -XX:+PrintFlagsFinal -version
:=标识不是默认,修改过(可能是jvm根据系统初始化、启动时添加参数)`
查看修改过的参数:java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=258496576 -XX:MaxHeapSize=4135945216 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
设置参数的方式
1、开发工具中设置比如IDEA,eclipse
2、运行jar包的时候:java -XX:+UseG1GC xxx.jar
3、web容器比如tomcat,可以在脚本中的进行设置
4、通过jinfo实时调整某个java进程的参数(参数只有被标记为manageable的flags可以被实时修改)
常见参数
参数 | 含义 | 说明 |
---|---|---|
-XX:CICompilerCount=3 | 最大并行编译数 | 如果设置大于1,虽然编译速度会提高,但是同样影响系统稳定性,会增加JVM崩溃的可能 |
-XX:InitialHeapSize=100M | 初始化堆大小 | 简写-Xms100M |
-XX:MaxHeapSize=100M | 最大堆大小 | 简写-Xms100M |
-XX:NewSize=20M | 设置年轻代的大小 | |
-XX:MaxNewSize=50M | 年轻代最大大小 | |
-XX:OldSize=50M | 设置老年代大小 | |
-XX:MetaspaceSize=50M | 设置方法区大小 | |
-XX:MaxMetaspaceSize=50M | 方法区最大大小 | |
-XX:+UseParallelGC | 使用UseParallelGC | 新生代,吞吐量优先 |
-XX:+UseParallelOldGC | 使用UseParallelOldGC | 老年代,吞吐量优先 |
-XX:+UseConcMarkSweepGC | 使用CMS | 老年代,停顿时间优先 |
-XX:+UseG1GC | 使用G1GC | 新生代,老年代,停顿时间优先 |
-XX:NewRatio | 新老生代的比值 | 比如-XX:Ratio=4,则表示新生代:老年代=1:4,也就是新生代占整个堆内存的1/5 |
-XX:SurvivorRatio | 两个S区和Eden区的比值 | 比如-XX:SurvivorRatio=8,也就是(S0+S1):Eden=2:8,也就是一个S占整个新生代的1/10 |
-XX:+HeapDumpOnOutOfMemoryError | 启动堆内存溢出打印 | 当JVM堆内存发生溢出时,也就是OOM,自动生成dump文件 |
-XX:HeapDumpPath=heap.hprof | 指定堆内存溢出打印目录 | 表示在当前目录生成一个heap.hprof文件 |
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:g1-gc.log | 打印出GC日志 | 可以使用不同的垃圾收集器,对比查看GC情况 |
-Xss128k | 设置每个线程的堆栈大小 | 经验值是3000-5000最佳 |
-XX:MaxTenuringThreshold=6 | 提升年老代的最大临界值 | 默认值为 15 |
-XX:InitiatingHeapOccupancyPercent | 启动并发GC周期时堆内存使用占比 | G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45. |
-XX:G1HeapWastePercent | 允许的浪费堆空间的占比 | 默认是10%,如果并发标记可回收的空间小于10%,则不会触发MixedGC。 |
-XX:MaxGCPauseMillis=200ms | G1最大停顿时间 | 暂停时间不能太小,太小的话就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。 |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量 | 默认值随JVM运行的平台不同而不同 |
-XX:G1MixedGCLiveThresholdPercent=65 | 混合垃圾回收周期中要包括的旧区域设置占用率阈值 | 默认占用率为 65% |
-XX:G1MixedGCCountTarget=8 | 设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数 | 默认8次混合垃圾回收,混合回收的目标是要控制在此目标次数以内 |
-XX:G1OldCSetRegionThresholdPercent=1 | 描述Mixed GC时,Old Region被加入到CSet中 | 默认情况下,G1只把10%的Old Region加入到CSet中 |
垃圾收集器
stw(stop the word):
停止用户线程,只执行垃圾回收线程;指的是Gc事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应
young区
Serial
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
适用范围:新生代
应用:Client模式下的默认新生代收集器
ParNew
优点:在多CPU时,比Serial效率高。
缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器
Parallel Scavenge(jdk8默认使用)
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,
看上去和ParNew一样,但是Parallel Scanvenge更关注系统的吞吐量
吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
比如虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,吞吐量=(100-1)/100=99%。
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。
old区
Serial Old
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程和Serial收集器一样。
Parallel Old(jdk8默认使用)
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。
CMS
采用的是"标记-清除算法",整个过程分为4步
初始标记:找出所有的GC root,标记直接相关联的第一个对象 STW
并发标记:找出所有的引用链上的剩余对象 耗时 并发执行
重新标记:就是将第二步所产生的垃圾进行二次标记 不耗时 STW
并发清理:清理所有垃圾 耗时 并发执行 清除不可达对象回收空间,同时有新垃圾产生,留着下次清理称为浮动垃圾
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存
回收过程是与用户线程一起并发地执行的。
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量
三色标记
三色标记
在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发
生。这里引入“三色标记”把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:
白色:
表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的
阶段, 仍然是白色的对象, 即代表不可达。
灰色:
表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
黑色:
表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过, 它是安全
存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象
标记过程:
1.初始时,所有对象都在 【白色集合】中;
2.将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
3.从灰色集合中获取对象:
4.将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
5.将本对象 挪到 【黑色集合】里面。
重复步骤3.4,直至【灰色集合】为空时结束。
结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收
多标-浮动垃圾:
在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过
(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮
动 垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。
另外,针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这
部分 对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。
内存碎片:
CMS采用的是“标记清除算法”,所以会产生内存碎片
漏标-读写屏障:漏标只有同时满足以下两个条件时才会发生
条件一:灰色对象 断开了 白色对象的引用;即灰色对象 原来成员变量的引用 发生了变化。
条件二:黑色对象 重新引用了 该白色对象;即黑色对象 成员变量增加了 新的引用。
一开始,垃圾收集器在定位垃圾时所有对象都是白色
从GC ROOT开始找,O1和O4都还持有至少一个引用,将O1和O4标记为灰色放入集合中,变为这样
在遍历灰色集合中的对象,发现O2和O5至少持有一个引用,放入集合;而O1和O4没有引用,标记为黑色,从集合中移除
重复操作遍历灰色集合,直到遍历到O2和O5,没有持有引用标记为黑色,移出集合;O3和O6标记为灰色,加入集合
最后发现遍历灰色集合,发现O3和O6都没有引用,标记为黑色,移出集合;剩下的白色O7和O8就是要回收的垃圾
G1
使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多
个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代
不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂
如果对象太大,一个Region放不下[超过Region大小的50%],那么就会直接放到H中
设置Region大小:-XX:G1HeapRegionSize=<N>M
所谓Garbage-Frist,其实就是优先回收垃圾最多的Region区域
(1)分代收集(仍然保留了分代的概念)
(2)空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
(3)可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内, 消耗在垃圾收集上的时间不得超过N毫秒)
工作过程可以分为如下几步
初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC 停顿时间制定回收计划
ZGC
常见问题:
(1)串行
-XX:+UseSerialGC
-XX:+UseSerialOldGC
(2)并行(吞吐量优先):
-XX:+UseParallelGC
-XX:+UseParallelOldGC
(3)并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
响应时间->stw越短,响应时间越好
吞吐量->运行用户代码时间/(运行用户代码时间+垃圾收集时间)
如何选择合适的垃圾收集器:
优先调整堆的大小让服务器自己来选择
如果内存小于100M,使用串行收集器
如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
如果允许停顿时间超过1秒,选择并行或JVM自己选
如果响应时间最重要,并且不能超过1秒,使用并发收集器
JVM调优
1、吞吐量:运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
2、响应时间:stw时间越短,响应时间越好
命令
top
linux系统中查看所有进程资源使用信息
top
top -Hp pid
:查看指定进程下的所有线程资源使用信息
这里仍然显示的是pid,实际上是线程号(十进制),如果后续要使用jstack命令,需要转成十六进制的数
jps
查看java进程信息,不管是windows还是Linux操作系统都通用
jps
jstack
查看java进程下所有线程运行的状况
jstack 进程号
我这里用一个死锁例子来展示
先使用jps查看java进程,找到死锁代码运行的进程号
再使用jstack 进程号查看该进程下所有线程的运行状况:jstack 2444
会显示所有的线程状况,包括GC垃圾回收线程;这里我写了一个死锁,jstack还帮忙发现了这个死锁
t1,t2线程都在等待对方释放锁,所以是blocked状态(只会发生在锁的竞争中,也就是synchronized)
jinfo
查看虚拟机的信息
jinfo 进程号
:查看虚拟机所有信息
jinfo -flag 参数名 进程号
:查看指定参数信息
jstat
jstat -class PID 1000 10
查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10次
jstat -gc PID 1000 10
:查看垃圾回收情况,每1000毫秒输出一次,共输出10次
jmap
jmap -heap PID
:打印出堆内存相关信息(old区、young区)
jmap -histo PID
:查看类及其实例内存使用情况
jmap -dump:format=b,file=heap.hprof PID
:dump出堆内存信息(生产上不要使用,会很影响进程)
一般在开发中,JVM参数可以加上下面两句,这样内存溢出时,会自动dump出该文件
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
工具
JDK自带分析工具jvisualvm(生产上不要使用)
windows版本下:在JDK目录下的bin目录下有这个工具
查看资源使用情况
查看线程情况
内存使用情况,这个很好定位OOM问题,排在前面的就是内存使用较多的
arthas
阿里巴巴出品的:官方文档 https://arthas.aliyun.com/doc/commands.html
常用命令:
1、jvm:查看当前JVM信息,跟jinfo命令类似
2、thread:查看当前线程信息,查看线程的堆栈,跟jstack命令类似
3、dashboard:当前系统的实时数据面板,跟top命令类似
4、heapdump:dump java heap, 类似jmap命令的heap dump功能
5、jad:反编译指定已加载类的源码,把class文件反编译为java文件,类似javap命令
6、mc:内存编译器,编译.java文件生成.class,跟javac命令一样
7、redefine/retransform:热替换,加载外部的.class文件,retransform jvm已加载的类,
在不重启程序的情况下动态改变方法
不允许新增加field/method、正在跑的函数,没有退出不能生效