• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于java:太好用了斩获3个大厂Offer后才发现学霸给的JVM笔记有多强大

java 搞代码 3年前 (2022-01-28) 22次浏览 已收录 0个评论
文章目录[隐藏]

Hello,明天给各位童鞋们分享JVM,连忙拿出小本子记下来吧!

垃圾回收场景

新生代GC场景

在jvm内存模型中,新生代的内存分为为Eden和两个Survivor

在零碎不停的运行过程中,Eden区会被塞满,这个时候就会触发Minor GC,进行垃圾回收有专门的垃圾回收线程,不同的内存区域会有不同的垃圾回收器,相当于垃圾回收线程和垃圾回收器配合起来,应用本人的垃圾回收算法,对指定的内存区域进行垃圾回收,如下图所示:

针对新生代采纳ParNew垃圾回收器来进行回收,而后ParNew垃圾回收器针对新生代采纳的就是复制算法来垃圾回收

这个时候垃圾回收器,就会把Eden区中的存活对象都标记进去,而后全副转移到Survivor1去,接着一次性清空掉Eden中的垃圾对象

当Eden再次塞满的时候,就又要触发Minor GC了,此时未然是垃圾回收线程运行垃圾回收器中的算法逻辑,也就是采纳复制算法逻辑,去标记进去Eden和Survivor1中的存活对象,而后一次性把存活对象转移到Survivor2中去,接着把Eden和Survivor1中的垃圾对象都回收掉

在产生GC的时候,咱们写好的JAVA零碎在运行期间还能不能持续在新生代里创立新的对象?

如果在GC期间,容许创立新的对象,那么垃圾回收器在把Eden和Survivor1里的存活对象标记转移到Survivor2去,而后还在想方法把Eden和Survivor1里的垃圾对象都清理掉,后果这个时候零碎程序还在不停的在Eden里创立新的对象,那么这些新对象很快就成了垃圾对象,有的还有人援用是存活对象,这对垃圾回收器齐全乱套,一边回收一边还在创立新的对象。

Stop the World

JVM最大的痛点,就是垃圾回收的过程,在垃圾回收的时候,尽可能让垃圾回收器分心的工作,不能轻易让咱们的Java利用持续创建对象,所以此时JVM会在后盾进入“入“Stop the World”状态,也就是说会间接进行咱们的Java零碎的所有工作线程,让咱们的代码不再运行

这样的话,就能够让咱们的零碎暂停运行,而后不再创立新的对象,同时让垃圾回收线程尽快实现垃圾回收的工作,就是标记和转移Eden以及Survivor1的存活对象到Survivor2中去,而后尽快一次性回收掉Eden和Survivor1中的垃圾对象,等垃圾回收结束后,持续复原咱们写的Java零碎的工作线程,而后持续运行咱们的代码逻辑,持续在Eden区创立新的对象

Stop the World造成的零碎进展

在运行GC的时候会无奈创立新的对象,则会造车零碎进展,如果Minor GC要运行50ms,则可能会导致咱们的零碎在50ms内不能承受任何申请,在这50ms期间用户发动的所有申请都会呈现短暂的卡顿,因为零碎的工作线程不在运行,不能解决申请

可能因为内存调配不合理,导致对象频繁进入老年代,均匀七八分钟一次Full GC,而Full GC比较慢,一次回收可能须要几秒甚至几十秒,所以一旦频繁的Full GC,就会造成零碎每隔几分钟卡死个几十秒,让用户体验极差

所以说,无论是新生代GC还是老年代GC,都尽量不要让频率过高,也防止持续时间过长,防止影响零碎失常运行,这也是应用JVM过程中一个最须要优化的中央,也是最大的一个痛点。

不同的垃圾回收器的不同的影响

Serial垃圾回收器(新生代)

用一个线程进行垃圾回收,而后此时暂停零碎工作线程

个别咱们在服务器程序中很少用这种形式

ParNew垃圾回收器(新生代)

罕用的新生代垃圾回收器

针对服务器个别都是多核CPU做了优化,他是反对多线程个垃圾回收的,能够大幅度晋升回收的性能,缩短回收的工夫

垃圾回收器

Serial和Serial Old垃圾回收器

别离用来回收新生代和老年代的垃圾对象

工作原理就是单线程运行,垃圾回收的时候会进行咱们本人写的零碎的其余工作线程,让咱们零碎间接卡死不动,而后让他们垃圾回收,这个当初个别写后盾Java零碎简直不必。

ParNew和CMS垃圾回收器

ParNew当初个别都是用在新生代的垃圾回收器,采纳的就是复制算法来垃圾回收

CMS是用在老年代的垃圾回收器

都是多线程并发的机制,性能更好,当初个别是线上生产零碎的标配组合

ParNew

实践
没有最新的G1垃圾回收器的话,通常大家线上零碎都是ParNew垃圾回收器作为新生代的垃圾回收器当然当初即便有了G1,其实很多线上零碎还是用的ParNew

通常运行在服务器上Java零碎,都能够充分利用服务器的多核CPU劣势,如果对新生代回收的时候,仅仅应用单线程进行垃圾回收,会导致节约CPU的资源

新生代的ParNew垃圾回收器主打的就是多线程垃圾回收机制,另外一种Serial垃圾回收器主打的是单线程垃圾回收,他们俩都是回收新生代的,惟一的区别就是单线程和多线程的区别,然而垃圾回收算法是齐全一样

ParNew垃圾回收器如果一旦在适合的机会执行Minor GC的时候,就会把零碎程序的工作线程全副停掉,禁止程序持续运行创立新的对象,而后本人就用多个垃圾回收线程去进行垃圾回收,回收的机制和算法都是一样的

参数设置
部署到Tomcat时能够在Tomcat的catalina.sh中设置Tomcat的JVM参数,应用Spring Boot也能够在启动时指定JVM参数。

指定应用ParNew垃圾回收器

应用“-XX:+UseParNewGC”选项,只有退出这个选项,JVM启动之后对新生代进行垃圾回收的,就是ParNew垃圾回收器

ParNew垃圾回收器默认状况下的线程数量

一旦咱们指定了应用ParNew垃圾回收器之后,他默认给本人设置的垃圾回收线程的数量就是跟CPU的核数是一样的

如果你肯定要本人调节ParNew的垃圾回收线程数量,也是能够的,应用“-XX:ParallelGCThreads”参数即可,通过他能够设置线程的数量

CMS

实践
老年代抉择的垃圾回收器是CMS,他采纳的是标记清理算法

标记清理算法:先通过GC Roots的办法,看各个对象是否被GC Roots给援用,如果是的话,那就是存活对象,否则就是垃圾对象。先将垃圾对象都标记进去,而后一次性把垃圾对象都回收掉,这种办法最大问题:就是会造成很多内存碎片,这种内存碎片不大不小,可能放不下任何一个对象,则会造成内存节约

CMS的STW(Stop the World)问题:如果进行所有工作线程,而后缓缓的执行“标记-清理”算法,会导致系统卡死工夫过长,很多响应无奈解决。所以CMS垃圾回收器采取的是:垃圾回收线程和零碎工作线程尽量同时执行的模式来解决

如何实现零碎一边工作的同时进行垃圾回收?
CMS在执行一次垃圾回收的过程共分为4个阶段:

初始标记

并发标记

从新标记

并发清理

1、初始标记
CMS在进行垃圾回收时,会先执行初始标记阶段。这个阶段会让零碎的工作线程全副进行,进入“Stop The World”状态,初始标记执行STW影响不大,因为他的速度比拟快,只是标记出GC Roots间接利用的对象

2、并发标记
这个阶段会让零碎能够随便创立各种新对象,持续运行,在运行期间可能会创立新的存活对象,也可能会让局部存活对象失去援用,变成垃圾对象。在这个过程中,垃圾回收线程,会尽可能的对已有的对象进行GC Roots追踪,然而这个过程中,在进行并发标记的时候,零碎程序会不停的工作,他可能会各种创立进去新的对象,局部对象可能成为垃圾

这个阶段来源gao*daima.com搞@代#码网就是对老年代所有对象进行GC Roots追踪,其实是最耗时的,须要追踪所有对象是否从本源上被GC Roots援用了,然而这个最耗时的阶段,是跟零碎程序并发运行的,所以其实这个阶段不会对系统运行造成影响。

3、从新标记
因为第二阶段里,你一边标记存活对象和垃圾对象,一边零碎在不停运行创立新对象,让老对象变成垃圾,所以第二阶段完结之后,相对会有很多存活对象和垃圾对象,是之前第二阶段没标记进去的。所以此时进入第三阶段,要持续让零碎程序停下来,再次进入“Stop the World”阶段。而后从新标记下在第二阶段里新创建的一些对象,还有一些已有对象可能失去援用变成垃圾的状况

这个从新标记的阶段,是速度很快的,他其实就是对在第二阶段中被零碎程序运行变动过的多数对象进行标记,所以运行速度很快,接着从新复原零碎程序的运行。

4、并发清理
让零碎程序随便运行,而后他来清理掉之前标记为垃圾的对象,这个阶段比拟耗时,须要进行对象的清理,然而他是跟着零碎程序并发运行的,所以也不影响零碎程序的执行

CMS垃圾回收器问题

1、并发回收导致CPU资源缓和
CMS垃圾回收器有一个最大的问题,尽管能在垃圾回收的同时让零碎同时工作,在并发标记和并发清理两个最耗时的阶段,垃圾回收线程和零碎工作线程同时工作,会导致无限的CPU资源被垃圾回收线程占用了一部分

并发标记的时候,须要对GC Roots进行深度追踪,看所有对象外面到底有多少人是存活的然而因为老年代里存活对象是比拟多的,这个过程会追踪大量的对象,所以耗时较高。并发清理,又须要把垃圾对象从各种随机的内存地位清理掉,也是比拟耗时的

所以在这两个阶段,CMS的垃圾回收线程是比拟消耗CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4

2、Concurrent Mode Failure问题
在并发清理阶段,CMS只不过是回收之前标记好的垃圾对象,然而这个阶段零碎始终在运行,可能会随着零碎运行让一些对象进入老年代,同时还变成垃圾对象,这种垃圾对象是“浮动垃圾”。因为他尽管成为了垃圾,然而CMS只能回收之前标记进去的垃圾对象,不会回收他们,须要等到下一次GC的时候才会回收他们。所以为了保障在CMS垃圾回收期间,还有肯定的内存空间让一些对象能够进入老年代,个别会预留一些空间。CMS垃圾回收的触发机会,其中有一个就是当老年代内存占用达到肯定比例了,就主动执行GC。

“-XX:CMSInitiatingOccupancyFaction”参数能够用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6外面默认的值是92%

也就是说,老年代占用了92%空间了,就主动进行CMS垃圾回收,预留8%的空间给并发回收期间,零碎程序把一些新对象放入老年代中。

那么如果CMS垃圾回收期间,零碎程序要放入老年代的对象大于了可用内存空间,此时会如何?

这个时候,会产生Concurrent Mode Failure,就是说并发垃圾回收失败了,我一边回收,你一边把对象放入老年代,内存都不够

此时就会主动用“Serial Old”垃圾回收器代替CMS,就是间接强行把零碎程序“Stop the World”,从新进行长时间的GC Roots追踪,标记进去全副垃圾对象,不容许新的对象产生,而后一次性把垃圾对象都回收掉,完预先再复原零碎线程

3、内存碎片问题
老年代的CMS采纳“标记-清理”算法,每次都是标记进去垃圾对象,而后一次性回收掉,这样会导致大量的内存碎片产生。如果内存碎片太多,会导致后续对象进入老年代找不到可用的间断内存空间了,而后触发Full GC

所以CMS不是齐全就仅仅用“标记-清理”算法的,因为太多的内存碎片实际上会导致更加频繁的Full GC

CMS有一个参数是“-XX:+UseCMSCompactAtFullCollection”,默认就关上,意思是在Full GC之后要再次进行“Stop the World”,进行工作线程,而后进行碎片整顿,就是把存活对象挪到一起,空进去大片间断内存空间,防止内存碎片

还有一个参数是“-XX:CMSFullGCsBeforeCompaction”,这个意思是执行多少次Full GC之后再执行一次内存碎片整顿的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理

触发老年代GC的机会

1、老年代可用内存小于新生代全副对象的大小,如果没开启空间担保参数,会间接触发Full GC,所以个别空间担保参数都会关上;

2、老年代可用内存小于历次新生代GC后进入老年代的均匀对象大小,此时会提前Full GC;

3、新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足;

4、-XX:CMSInitiatingOccupancyFaction:老年代的已用内存大于设定的阀值,就会触发Full GC;

5、显示调用System.gc

ParNew + CMS带给咱们的痛点是什么

Stop the World,这个是大家最痛的一个点

无论是新生代垃圾回收,还是老年代垃圾回收,都会或多或少产生“Stop the World”景象,对系统的运行是有肯定影响的。所以其实之后对垃圾回收器的优化,都是朝着缩小“Stop the World”的指标去做的。

在这个根底之上,G1垃圾回收器就应运而生了,他能够提供比“ParNew + CMS”组合更好的垃圾回收的性能

G1垃圾回收器

特点
G1垃圾回收器是能够同时回收新生代和老年代的对象的,不须要两个垃圾回收器配合起来运作,他一个人就能够搞定所有的垃圾回收。

1、把Java堆内存拆分为多个大小相等的Region

G1也会有新生代和老年代的概念,然而只不过是逻辑上的概念

也就是说新生代可能蕴含了某些Region,老年代可能蕴含了某些Region。

2、能够设置一个垃圾回收的预期进展工夫

也就是说比方咱们能够指定:心愿G1在垃圾回收的时候,能够保障,在1小时内由G1垃圾回收导致的“Stop the World”工夫,也就是零碎进展的工夫,不能超过1分钟,这样相当于咱们就能够间接管制垃圾回收对系统性能的影响

3、Region可能属于新生代也可能属于老年代

刚开始Region可能谁都不属于,而后接着就调配给了新生代,而后放了很多属于新生代的对象,接着就触发了垃圾回收这个Region,下一次同一个Region可能又被调配了老年代了,用来放老年代的长生存周期的对象,所以其实在G1对应的内存模型中,Region随时会属于新生代也会属于老年代,所以没有所谓新生代给多少内存,老年代给多少内存这一说

实际上新生代和老年代各自的内存区域是不停的变动的,由G1自动控制

G1是如何做到对垃圾回收导致的零碎进展可控的?

其实G1如果要做到这一点,他就必须要追踪每个Region里的回收价值,啥叫做回收价值呢?

他必须搞清楚每个Region里的对象有多少是垃圾,如果对这个Region进行垃圾回收,须要消耗多长时间,能够回收掉多少垃圾?G1通过追踪发现,1个Region中的垃圾对象有10MB,回收他们须要消耗1秒钟,另外一个Region中的垃圾对象有20MB,回收他们须要消耗200毫秒。

而后在垃圾回收的时候,G1会发现在最近一个时间段内,比方1小时内,垃圾回收曾经导致了几百毫秒的零碎进展了,当初又要执行一次垃圾回收,那么必须是回收上图中那个只须要200ms就能回收掉20MB垃圾的Region;于是G1触发一次垃圾回收,尽管可能导致系统进展了200ms,然而一下子回收了更多的垃圾,就是20MB的垃圾

所以简略来说,G1能够做到让你来设定垃圾回收对系统的影响,他本人通过把内存拆分为大量小Region,以及追踪每个Region中能够回收的对象大小和预估工夫,最初在垃圾回收的时候,尽量把垃圾回收对系统造成的影响管制在你指定的工夫范畴内,同时在无限的工夫内尽量回收尽可能多的垃圾对象。这就是G1的外围设计思路

如何设定G1对应的内存大小?

G1对应的是一大堆的Region内存区域,每个Region的大小是统一的,默认状况下主动计算和设置的,能够给整个堆内存设置一个大小,比如说用“-Xms”和“-Xmx”来设置堆内存的大小

JVM启动的时候,发现应用的是G1垃圾回收器(通过:用“-XX:+UseG1GC”来指定应用G1垃圾回收器),此时会主动用堆大小除以2048,JVM最多能够有2048个Region,而后Region的大小必须是2的倍数,比如说1MB、2MB、4MB之类,能够通过手动形式来指定,则是“-XX:G1HeapRegionSize“

刚开始的时候,默认新生代对堆内存的占比是5%,这个是能够通过“-XX:G1NewSizePercent”来设置新生代初始占比的,其实维持这个默认值即可

在零碎运行中,JVM其实会不停的给新生代减少更多的Region,然而最多新生代的占比不会超过60%,能够通过“-XX:G1MaxNewSizePercent”,而且一旦Region进行了垃圾回收,此时新生代的Region数量还会缩小,这些其实都是动静

新生代还有Eden和Survivor的概念?

G1中尽管把内存划分为很多的 Region,然而其实还是有新生代、老年代的辨别,而且新生代里还是有Eden和Survivor的划分

通过参数,“-XX:SurvivorRatio=8”,能够设置新生代中80%的Region属于Eden,两个Survivor各自占10%
随着对象不停的在新生代里调配,属于新生代的Region会一直减少,Eden和Survivor对应的Region也会一直减少

G1的新生代垃圾回收触发机制?

既然G1的新生代也有Eden和Survivor的辨别,那么触发垃圾回收的机制都是相似的,随着不停的在新生代的Eden对应的Region中放对象,JVM就会不停的给新生代退出更多的Region,直到新生代占据堆大小的最大比例60%。

一旦新生代达到了设定的占据堆内存的最大大小60%,这个时候还是会触发新生代的GC,G1就会用之前说过的复制算法来进行垃圾回收,进入一个“Stop the World”状态,而后把Eden对应的Region中的存活对象放入S1对应的Region中,接着回收掉Eden对应的Region中的垃圾对象,然而这个过程跟之前是有区别的,因为G1是能够设定指标GC进展工夫的,也就是G1执行GC的时候最多能够让零碎进展多长时间,能够通过“-XX:MaxGCPauseMills”参数来设定,默认值是200ms。

那么G1就会通过之前说的,对每个Region追踪回收他须要多少工夫,能够回收多少对象来抉择回收一部分的Region,保障GC进展工夫管制在指定范畴内,尽可能多的回收掉一些对象。

对象什么时候进入老年代?

能够说跟之前简直是一样的,还是这么几个条件:

1、对象在新生代躲过了很屡次的垃圾回收,达到了肯定的年龄了,“-XX:MaxTenuringThreshold”参数能够设置这个年龄,就会进入老年代

2、 动静年龄断定规定,如果一旦发现某次新生代GC过后,存活对象超过了Survivor的50%

大对象Region
在之前,大对象是间接进入老年代,在G1的内存模型中,G1提供了专门的Region来寄存大对象,而不是让大对象间接进入老年的Region中。

在G1中,大对象的断定规定就是一个大对象超过了一个Region大小的50%,如果每个Region是2MB,只有一个大对象超过了1MB,就会被放入大对象专门的Region中,而且一个大对象如果太大,可能会横跨多个Region来寄存在新生代、老年代回收的时候,会顺带带着大对象Region一起回收

​好啦,明天的文章就到这里,心愿能帮忙到屏幕前迷茫的你们!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:太好用了斩获3个大厂Offer后才发现学霸给的JVM笔记有多强大

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址