JVM-垃圾回收

java :

你只管扔垃圾就行了,有人帮你处理
GC处理垃圾开发效率高,执行效率低

C++:

手工处理垃圾,就会产生很多问题
忘记回收 ,容易内存泄漏
回收多次
非法访问
开发效率低,执行效率高

1.1 引用计数法

对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。
弊端 :如果AB相互持有引用,导致永远不能被回收。

1.2 可达性分析

通过GC Root的对象,开始向下寻找,看某个对象是否可达
在这里插入图片描述

能作为GC Root:
类加载器、Thread、虚拟机栈的本地的变量等。
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
在这里插入图片描述

2.1什么时候会垃圾回收

GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。
当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是
具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决
定。但是不建议手动调用该方法,因为GC消耗的资源比较大。
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
(1)当Eden区或者S区不够用了
(2)老年代空间不够用了
(3)方法区空间不够用了
(4)System.gc()

3.1 常用垃圾收集算法

Mark-Sweep(标记清除)

  1. 标记
    找出内存中需要回收的对象,并且把它们标记出来

此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时

在这里插入图片描述

  1. 清除
    清除掉被标记需要回收的对象,释放出对应的内存空间

在这里插入图片描述
缺点

(1)标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能序运行过程中需要分配较大对象时,无法找到足够的连续内存而(1)标记和清除两个过程都比较耗时,效率不高
(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

总结: 存活对象较多的情况下效率比较高。 这种是需要经过两遍扫描,第一遍扫描是找到那些有用的,第二遍扫描是把那些没用的找出来清理掉。执行效率上偏低一些,容易产生碎片。
Copying(拷贝)
将内存划分为两块相等的区域,每次只使用其中一块,如下图所示:
在这里插入图片描述

当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
在这里插入图片描述

缺点: 空间利用率降低。

总结:因为需要将存活的对象复制到另一片区域,所以适用于存活对象较少的情况,只扫描一次,效率提高没有碎片,空间浪费,移动复制对象,需要调整对象引用。

Mark-Compact(标记压缩)

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都有100%存活的极端情况,所以老年代一般不能直接选用这种算法。

标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
其实上述过程相对"复制算法"来讲,少了一个"保留区"在这里插入图片描述
让所有存活的对象都向一端移动,清理掉边界以外的内存。
在这里插入图片描述

3.JVM内存分代模型(用于分代垃圾回收算法)

1. 部分垃圾回收器使用的模型
1.分代
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代

2.新生代(堆) + 老年代(堆) + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace

永久代 元数据 - Class;
永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存);
字符串常量 1.7 - 永久代,1.8 - 堆;
MethodArea逻辑概念 - (1.7)永久代、元数据(1.8);

3.新生代 = Eden + 2个suvivor区
YGC回收之后,大多数的对象会被回收,活着的进入s0
再次YGC,活着的对象eden + s0 -> s1
再次YGC,eden + s1 -> s0
年龄足够 -> 老年代 (15 CMS 6)
s区装不下 -> 老年代

4.老年代
顽固分子
老年代满了FGC Full GC
5.GC Tuning (Generation)
尽量减少FGC
MinorGC = YGC
MajorGC = FGC

2. 堆内存逻辑分区

把内存分成这么几大块,第一大块是叫做新生代就是新诞生出来的,刚new出来的叫做新生代,也叫做young。老年代叫old也叫tenured(终身)。新生代是刚刚new出来的那些对象,老年代是垃圾回收的很多次都没把它回收掉的老顽固。
在这里插入图片描述

3. 对象从出生到消亡
一个对象产生之后首先进行栈上分配,栈上如果分配不下会进入伊甸区,伊甸区经过一次垃圾回收之后进入survivor区,survivor区在经过一次垃圾回收之后又进入另外一个survivor,与此同时伊甸区的某些对象也跟着进入另外一个survivor,什么时候年龄够了会进入到old区,这是整个的对象的一个逻辑上的移动过程。

在这里插入图片描述
通过参数:-XX:MaxTenuringThreshold配置。
在年轻代进行回收的叫MinorGC/YGC,年轻代空间耗尽时触发;
在老年代或者整个区域进行回收叫MajorGC/FullGC,在老年代无法继续分配空间时触发,新生代老年代同时进行回收。
-Xms-Xmx
-Xmn
eden
s0
s1
tenured/old
-Xms -Xmx: x是分标参数 m是memory s是最小值 x是最大值 n是new
4.栈上分配
C语言里面有struct这样的结构体,那么这种结构的东西是可以直接在栈上分配的。Java为了对标这一点,设计了一个栈上分配的理念。
1.线程私有小对象 :小对象,线程是私有的
2.无逃逸: 就在某一段代码中使用,出了这段代码就没有人认识它了
3.支持标量替换: 意思是用普通的属性、把普通的类型代替对象就叫标量替换
无需调整
栈上分配要比堆上分配要快,在栈上分配不下了,它会优先进行本地分配

5.线程本地分配TLAB (Thread Local Allocation Buffer)
在伊甸区好多线程都往里头分配对象,分配对象的时候你这个线程一定会进行空间的征用,谁抢到算谁的。多线程的同步,效率就会降低,所以设计了这么一个机制叫做TLAB
1:占用eden,默认1%,在伊甸区取用百分之一的空间,这块空间叫做这个线程独有。分配对象的时候首先往我线程独有的这块空间里进行分配。
2:多线程的时候不用竞争eden就可以申请空间, 提高效率。
小对象
无需调整
老年代
大对象
eden
逃逸:方法内部new了一个没有任何引用指向的对象,这个对象逃不出这个方法,所以就逃逸了;如果这个对象被方法外的引用引用了,就逃逸了。

-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB
-XX:-DoEscapeAnalysis 去掉逃逸分析
-XX:-EliminateAllocations 去掉标量替换
-XX:-UseTLAB 去掉TLAB```
new 对象会变慢 栈比堆快 栈弹出对象就没了 不用垃圾回收 线程本地分配快

对象何时进入老年代
1.超过 XX:MaxTenuringThreshold 指定次数(YGC)
– Parallel Scavenge 15
– CMS 6
– G1 15
最大是15 因为对象头4;
2.动态年龄 垃圾回收s1 s2超过50%进入old区
– s1 - > s2超过50%。两个s之间拷贝来拷贝去只要超过百分之50的时候把年龄最大的直接就放入到
old区,也就是不一定非得到15岁,不一定非得到6岁
– 把年龄最大的放入O

在s1里面有这么多对象拷贝到了s2里面超过了百分之50的话,s1里
面在加上伊甸区里面,整个一个对象一下子拷贝到s2里,经过一次垃圾回收(YGC)。过去之后,这个时候整个加起来对象已经超过s2的一半了,这里面年龄最大的一些对象直接进入old区,这叫做动态年龄的一个判断。
在这里插入图片描述
Eden区加上s1扔到s2,整个对象超过survior区50% 直接把年龄大的扔到old区
在这里插入图片描述
总结:对象分配过程图如下: start 会new一个新的对象,首先在栈上分配,如果能分配就会分配到栈上,栈上有个好处直接往里一弹搞定,弹出来之后结束。如果分配不下,就会判断大不大(用一个参数来指定的),如果特别大,直接进入old区(FGC才会结束 ),如果不够大,会进去线程本地分配TLAB,到伊甸区(E),进行GC清除,如果清完了结束,如果没有清完进s1,s1在进行GC的清除,如果年龄够了进入old区 ,如果不够进s2。

JAVA 命令查看参数:

java 回车//查看java参数

-横杠开头都是标志参数
-X是非标参数
-XX是不稳定参数
-Xms:起始的java堆大小
-Xmx:最大的java堆大小
java的参数

java -XX:PrintFlagsFinal -version//打印所有参数代码

Jvm误区–动态对象年龄判定

虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才 能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

这里讲的是动态年龄的判定。对于动态的判定的条件就是相同年龄所有对象大小的总和大于Survivor空间的一半,然后算出的年龄要和MaxTenuringThreshold的值进行比较,以此保证MaxTenuringThreshold设置太大(默认15),导致对象无法晋升。

问题提出场景假设:如果说非得相同年龄所有对象大小总和大于Survivor空间的一半才能晋升。我们看
下面的场景:

MaxTenuringThreshold为15
年龄1的对象占用了33%
年龄2的对象占用33%
年龄3的对象占用34%。
开始推论:按照晋升的标准。首先年龄不满足MaxTenuringThreshold,不会晋升。每个年龄的对象都
不满足50%。不会晋升。
得到假设结论:Survivor都占用了100%了,但是对象就不晋升。导致老年代明明有空间,但是对象就
停留在年轻代。但这个结论似乎与jvm的表现不符合,只要老年代有空间,最后还会晋升的。

uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//survivor_capacity是survivor空间的大小
size_t desired_survivor_size = (size_t)((((double)
survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
total += sizes[age];//sizes数组是每个年龄段对象大小
if (total > desired_survivor_size) break;
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}

把晋升年龄计算的代码摘出。我们来看看动态年龄的计算。代码中有一个TargetSurvivorRatio的值。-XX:TargetSurvivorRatio 目标存活率,默认为50%通过这个比率来计算一个期望值,desired_survivor_size 。然后用一个total计数器,累加每个年龄段对象大小的总和。当total大于desired_survivor_size停止。然后用当前age和MaxTenuringThreshold 对
比找出最小值作为结果总体表征就是,年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域TargetSurvivorRatio的时候,就从这个年龄段网上的年龄的对象进行晋升。
再次推演
还是上面的场景。 年龄1的占用了33%,年龄2的占用了33%,累加和超过默认的TargetSurvivorRatio(50%),年龄2和年龄3的对象都要晋升。
小结
动态对象年龄判断,主要是被TargetSurvivorRatio这个参数来控制。而且算的是年龄从小到大的累加和,而不是某个年龄段对象的大小。看完后先记住这个参数TargetSurvivorRatio,虽然以后基本不会调整他。

分配担保:YGC期间 survivor区空间不够了 通过空间担保直接进入老年代。

5.常见垃圾回收器

常用垃圾回收器:
在这里插入图片描述
Stop The World
stop-the-world,简称STw,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。
可达性分析算法中枚举根节点(GC Roots)会导致所有Java执行线程停顿。
分析工作必须在一个能确保一致性的快照中进行一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证被STW中断的应用程序线程会在完成GC之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样,所以我们需要减少STw的发生。
STW事件和采用哪款GC无关所有的GC都有这个事件。
哪怕是G1也不能完全避免Stop-the-world情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
STW是JVM在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。

1.Serial:
单线程STW,单CPU效率最高,虚拟机是client模式默认垃圾回收器。
当工作的时候所有工作线程全部停止, 当工作的时候断开的线程则是垃圾 ,如果突然加入Serial则停止,进行清理垃圾,safe point = 线程停止 (需要找到一个安全点上线程停止),因为停顿时间较长 所有Serial现在用的较少。
在这里插入图片描述
它是一种**单线程收集器,**不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。

优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
适用范围:新生代
应用:Client模式下的默认新生代收集器

2.Serial old
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程和Serial收集器一样。
在这里插入图片描述

3.Parallel Scavenge

如果你在JVM没有做任何调优的话,默认的就是Parallel Scavenge和ParallelOld简称PS+PO。
Parallel Scavenge 和 Serial 区别 : Parallel Scavenge是多线程清理垃圾。
在这里插入图片描述
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是Parallel Scanvenge更关注系统的吞吐量。

吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
比如虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,
吞吐量=(100-1)/100=99%。
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCRatio直接设置吞吐量的大小。

4.Parallel Old

  • a compacting collector that uses multiple GC threads.
  • 整理算法
    在这里插入图片描述

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。

5.ParNew
在这里插入图片描述
可以把这个收集器理解为Serial收集器的多线程版本。

优点:在多CPU时,比Serial效率高。PN响应时间优先
缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器

CMS
从线程的角度理解 ,它垃圾回收的线程和工作线程同时进行,叫做concurrent mark sweep
(concurrent 并发)。不管你用几个线程进行垃圾回收这个过程都太长了。在内存比较小的情况下,没有
问题,速度很快。但是现在的服务器内存越来越大,大到什么程度,原来是一个房间,现在可以看成一个天安门广场。作为一个这么大的内存无论你多少个线程来清理一遍也得需要特别长的时间。以前大概在有10G内存的时候他用PS+PO停顿时间清理一次,大概需要11秒钟。 有人用CMS,这个最后也会产生碎片之后产生FGC,FGC默认的STW最长的到10几个小时,就直接卡死在哪儿了,大家什么都干不了。
在这里插入图片描述

第一个阶段叫做CMS initial mark(初始标记阶段)。很简单,就是我直接找到最根上的对象,其他的对象我不标记,直接标记最根上的
第二个是CMS concurrent mark(并发标记),据统计百分之八十的GC的时间是浪费在这里,因此它把这块最浪费时间的和我们的应用程序同时运行,对客户来说感觉可能是慢了一些,但至少你还有反应。这是并发标记。就是你一边产生垃圾,我这一边跟着标记但是这个过程是很难完成的。
最后又有一个CMS remark(重新标记)。这又是一个STW。在并发标记过程中产生的那些新的垃圾在重新标记里头给它标记一下,这个时候需要你们俩停一下,时间不长。
最后是一个concurrent sweep(并发清理)的过程。并发清理也有它的问题,并发清理过程也会产生新
的垃圾啊,这个时候的垃圾叫做浮动垃圾,浮动垃圾就得等着下一次CMS再一次运行的过程把它给清理掉。

CMS产生的四个阶段解释:
1.初始标记STW开始的标记 单线程
2.并发标记和应用程序同时运行 最浪费时间 和应用程序同时运行 边产生垃圾边标记
3.重新标记又是一个STW,在并发标记中产生的新垃圾在重新标记中标记 标记并发标记新产生的垃圾 多线程
4.并发清理也会产生新的垃圾,叫做浮动垃圾 清理时产生的垃圾叫浮动垃圾 浮动垃圾下次垃圾回收的时候回收

什么条件触发CMS呢?老年代分配不下了,处理不下会触动CMS。初始标记是单线程,重新标记是多线程。
老年代分配不下触发CMS
▪ 并发标记扫描 :垃圾回收和工作线程同时
▪ 一个主要是并发的、低暂停的收集器。

CMS的缺点:cms出现问题会调用Serial Old老年代出来使用单个线程进行标记压缩

CMS的两大问题:
1.Memory Fragmentation

-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩

内存的碎片,是比较严重的问题。你可想象如果你的内存超级大。CMS设计出来就对付几百兆内存,到上G内存。现在有很多人拿它来应付很大的内存就会出问题。因为一旦老年代产生了很多碎片的时候,然后从年轻代过来的这些对象已经找不到空间了,这叫PromotionFailed 找不到空间了。这时候它干了这么一件事 Serial Old请出来,让它用一个线程在这里面做标记压缩。
2.Floating Garbage
Concurrent Mode Failure 产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
浮动的垃圾 ,当出现Concurrent Mode Failure和PromotionFailed时说明碎片
较多,里面内存分配不下,会调用Serial Old。并不是说当你使用CMS之后这个东西没有办法避免,
可以降低触发CMS的阈值。
解决方案类似,保持老年代有足够的空间

–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

G1(10ms) 算法:三色标记 + SATB
ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
Shenandoah 算法:ColoredPointers + WriteBarrier

6.垃圾收集器跟内存大小的关系

Serial 几十兆

PS 上百兆 - 几个G

CMS - 20G

G1 - 上百G

ZGC - 4T - 16T(JDK13)

1.8默认的垃圾回收:PS + ParallelOld

7.常见垃圾回收器组合参数设定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
    小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器

  • -XX:+UseParNewGC = ParNew + SerialOld
    这个组合已经很少用(在某些版本中已经废弃)
    https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future

  • -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old

  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】

  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

  • -XX:+UseG1GC = G1

  • Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
    java +XX:+PrintCommandLineFlags -version
    通过GC的日志来分辨

  • Linux下1.8版本默认的垃圾回收器到底是什么?
    1.8.0_181 默认(看不出来)Copy MarkCompact
    1.8.0_222 默认 PS + PO

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值