乐趣区

关于jvm:阿里终面每天100w次登陆请求-8G-内存该如何设置JVM参数

大家好,我是不才陈某~

上周常识星球的同学在阿里云技术面终面的时候被问到这么一个问题:假如一个每天 100w 次登陆申请的平台,一个服务节点 8G 内存,该如何设置 JVM 参数? 感觉答复的不太现实,过去找我复盘。

上面以面试题的模式给大家梳理进去,做到一举两得:

  • 既供大家实操参考
  • 又供大家面试参考

大家要学习的,除了 JVM 配置计划 之外,是其 剖析问题的思路、思考问题的视角。这些思路和视角,能帮忙大家走更远、更远。

接下来,进入正题。

关注公众号:码猿技术专栏,回复关键词:1111 获取阿里外部 Java 性能调优手册!

每天 100w 次登陆申请, 8G 内存该如何设置 JVM 参数?

每天 100w 次登陆申请, 8G 内存该如何设置 JVM 参数,大略能够分为以下 8 个步骤

Step1:新零碎上线如何布局容量?

1. 套路总结

任何新的业务零碎在上线以前都须要去估算服务器配置和 JVM 的内存参数,这个容量与资源布局并不仅仅是零碎架构师的随便估算的,须要依据零碎所在业务场景去估算,推断进去一个零碎运行模型,评估 JVM 性能和 GC 频率等等指标。以下是我联合大牛教训以及本身实际来总结进去的一个建模步骤:

  • 计算业务零碎每秒钟创立的对象会佔用多大的内存空间,而后计算集群下的每个零碎每秒的内存佔用空间(对象创立速度)
  • 设置一个机器配置,估算新生代的空间,比拟不同新生代大小之下,多久触发一次 MinorGC。
  • 为了防止频繁 GC,就能够从新估算须要多少机器配置,部署多少台机器,给 JVM 多大内存空间,新生代多大空间。
  • 依据这套配置,根本能够推算出整个零碎的运行模型,每秒创立多少对象,1s 当前成为垃圾,零碎运行多久新生代会触发一次 GC,频率多高。

2. 套路实战——以登录零碎为例

有些同学看到这些步骤还是发憷,说的如同是那么回事,一到理论我的项目中到底怎麽做我还是不晓得!

光说不练假把式,以登录零碎为例模仿一下推演过程:

  • 假如每天 100w 次登陆申请,登陆峰值在早上,预估峰值期间每秒 100 次登陆申请。
  • 假如部署 3 台服务器,每台机器每秒解决 30 次登陆申请,假如一个登陆申请须要解决 1 秒钟,JVM 新生代里每秒就要生成 30 个登陆对象,1s 之后申请结束这些对象成为了垃圾。
  • 一个登陆申请对象假如 20 个字段,一个对象估算 500 字节,30 个登陆佔用大概 15kb,思考到 RPC 和 DB 操作,网络通信、写库、写缓存一顿操作下来,能够扩充到 20-50 倍,大概 1s 产生几百 k -1M 数据。
  • 假如 2C4G 机器部署,调配 2G 堆内存,新生代则只有几百 M,依照 1s1M 的垃圾产生速度,几百秒就会触发一次 MinorGC 了。
  • 假如 4C8G 机器部署,调配 4G 堆内存,新生代调配 2G,如此须要几个小时才会触发一次 MinorGC。

所以,能够粗略的推断进去一个每天 100w 次申请的登录零碎,依照 4C8G 的 3 实例集群配置,调配 4G 堆内存、2G 新生代的 JVM,能够保障系统的一个失常负载。

基本上把一个新零碎的资源评估了进去,所以搭建新零碎要每个实例须要多少容量多少配置,集群配置多少个实例等等这些,并不是拍拍脑袋和胸脯就能够决定的下来的。

Step2:该如何进行垃圾回收器的抉择?

吞吐量还是响应工夫

首先引入两个概念:吞吐量和低提早

吞吐量 = CPU 在用户利用程序运行的工夫 /(CPU 在用户利用程序运行的工夫 + CPU 垃圾回收的工夫)

响应工夫 = 均匀每次的 GC 的耗时

通常,吞吐优先还是响应优先这个在 JVM 中是一个两难之选。

堆内存增大,gc 一次能解决的数量变大,吞吐量大;然而 gc 一次的工夫会变长,导致前面排队的线程等待时间变长;相同,如果堆内存小,gc 一次工夫短,排队期待的线程等待时间变短,提早缩小,但一次申请的数量变小(并不相对合乎)。

无奈同时兼顾,是吞吐优先还是响应优先,这是一个须要衡量的问题。

垃圾回收器设计上的考量

  • JVM 在 GC 时不容许一边垃圾回收,一边还创立新对象(就像不能一边打扫卫生,还在一边扔垃圾)。
  • JVM 须要一段 Stop the world 的暂停工夫,而 STW 会造成零碎短暂进展不能解决任何申请;
  • 新生代收集频率高,性能优先,罕用复制算法;老年代频次低,空间敏感,防止复制形式。
  • 所有垃圾回收器的波及指标都是要让 GC 频率更少,工夫更短,缩小 GC 对系统影响!

CMS 和 G1

目前支流的垃圾回收器配置是新生代采纳 ParNew,老年代采纳 CMS 组合的形式,或者是齐全采纳 G1 回收器,

从将来的趋势来看,G1 是官网保护和更为推崇的垃圾回收器。

业务零碎:

  • 提早敏感的举荐 CMS;
  • 大内存服务,要求高吞吐的,采纳 G1 回收器!

CMS 垃圾回收器的工作机制

CMS 次要是针对老年代的回收器,老年代是标记 - 革除,默认会在一次 FullGC 算法后做整顿算法,清理内存碎片。

CMS GC 形容 Stop the world 速度
1. 开始标记 初始标记仅标记 GCRoots 能间接关联到的对象,速度很快 Yes 很快
2. 并发标记 并发标记阶段就是进行 GCRoots Tracing 的过程 No
3. 从新标记 从新标记阶段则是为了修改并发标记期间因用户程序持续运作而导致标记产生变动的那一部分对象的标记记录。 Yes 很快
4. 垃圾回收 并发清理垃圾对象(标记革除算法) No
  • 长处:并发收集、主打“低延时”。在最耗时的两个阶段都没有产生 STW,而须要 STW 的阶段都以很快速度实现。
  • 毛病:1、耗费 CPU;2、浮动垃圾;3、内存碎片
  • 实用场景:器重服务器响应速度,要求零碎进展工夫最短。

总之:

业务零碎,提早敏感的举荐 CMS;

大内存服务,要求高吞吐的,采纳 G1 回收器!

Step3:如何对各个分区的比例、大小进行布局

个别的思路为:

首先,JVM 最重要最外围的参数是去评估内存和调配,第一步须要指定堆内存的大小,这个是零碎上线必须要做的,-Xms 初始堆大小,-Xmx 最大堆大小,后盾 Java 服务中个别都指定为零碎内存的一半,过大会佔用服务器的系统资源,过小则无奈施展 JVM 的最佳性能。

其次,须要指定 -Xmn 新生代的大小,这个参数十分要害,灵便度很大,尽管 sun 官网举荐为 3 / 8 大小,然而要依据业务场景来定,针对于无状态或者轻状态服务(当初最常见的业务零碎如 Web 利用)来说,个别新生代甚至能够给到堆内存的 3 / 4 大小;而对于有状态服务(常见如 IM 服务、网关接入层等零碎)新生代能够依照默认比例 1 / 3 来设置。服务有状态,则意味著会有更多的本地缓存和会话状态信息常驻内存,应为要给老年代设置更大的空间来寄存这些对象。

最初,是设置 -Xss 栈内存大小,设置单个线程栈大小,默认值和 JDK 版本、零碎无关,个别默认 512~1024kb。一个后盾服务如果常驻线程有几百个,那麽栈内存这边也会佔用了几百 M 的大小。

JVM 参数 形容 默认 举荐
-Xms Java 堆内存的大小 OS 内存 64/1 OS 内存一半
-Xmx Java 堆内存的最大大小 OS 内存 4 /1 OS 内存一半
-Xmn Java 堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了 跌认堆的 1 /3 sun 举荐 3 /8
-Xss 每个线程的栈内存大小 和 idk 无关 sun

对于 8G 内存,个别调配一半的最大内存就能够了, 因为机器本上还要占用肯定内存,个别是调配 4G 内存给 JVM,

引入性能压测环节,测试同学对登录接口压至 1s 内 60M 的对象生成速度,采纳 ParNew+CMS 的组合回收器,

失常的 JVM 参数配置如下:

-Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 

这样设置可能会因为 动静对象年龄判断准则 导致频繁 full gc。为啥呢?

压测过程中,短时间(比方 20S 后)Eden 区就满了,此时再运行的时候对象曾经无奈调配,会触发 MinorGC,

假如在这次 GC 后 S1 装入 100M,马上过 20S 又会触发一次 MinorGC,多进去的 100M 存活对象 +S1 区的 100M 曾经无奈顺利放入到 S2 区,此时就会触发 JVM 的动静年龄机制,将一批 100M 左右的对象推到老年代保留,继续运行一段时间,零碎可能一个小时候内就会触发一次 FullGC。

依照默认 8:1:1 的比例来调配时, survivor 区只有 1G 的 10% 左右,也就是几十到 100M,

如果 每次 minor GC 垃圾回收过后进入 survivor 对象很多,并且 survivor 对象大小很快超过 Survivor 的 50%,那么会触发动静年龄断定规定,让局部对象进入老年代.

而一个 GC 过程中,可能局部 WEB 申请未处理完毕, 几十兆对象,进入 survivor 的概率,是十分大的,甚至是肯定会产生的.

如何解决这个问题呢?为了让对象尽可能的在新生代的 eden 区和 survivor 区, 尽可能的让 survivor 区内存多一点, 达到 200 兆左右,

于是咱们能够更新下 JVM 参数设置:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M  -XX:SurvivorRatio=8  

阐明:‐Xmn2048M ‐XX:SurvivorRatio=8 
年老代大小 2g,eden 与 survivor 的比例为 8:1:1,也就是 1.6g:0.2g:0.2g

survivor 达到 200m,如果几十兆对象到底 survivor,survivor 也不肯定超过 50%

这样能够避免每次垃圾回收过后,survivor 对象太早超过 50% ,

这样就升高了因为对象动静年龄判断准则导致的对象频繁进入老年代的问题,

什么是 JVM 动静年龄判断规定呢?

对象进入老年代的 动静年龄判断规定(动静降职年龄计算阈值):Minor GC 时,Survivor 中年龄 1 到 N 的对象大小超过 Survivor 的 50% 时,则将大于等于年龄 N 的对象放入老年代。

外围的优化策略是:是让短期存活的对象尽量都留在 survivor 里,不要进入老年代,这样在 minor gc 的时候这些对象都会被回收,不会进到老年代从而导致 full gc

应该如何去评估新生代内存和调配适合?

这里特地说一下,JVM 最重要最外围的参数是去评估内存和调配,

第一步须要指定堆内存的大小,这个是零碎上线必须要做的,-Xms 初始堆大小,-Xmx 最大堆大小,

后盾 Java 服务中个别都指定为零碎内存的一半,过大会佔用服务器的系统资源,过小则无奈施展 JVM 的最佳性能。

其次须要指定 -Xmn 新生代的大小,这个参数十分要害,灵便度很大,尽管 sun 官网举荐为 3 / 8 大小,然而要依据业务场景来定:

  • 针对于无状态或者轻状态服务(当初最常见的业务零碎如 Web 利用)来说,个别新生代甚至能够给到堆内存的 3 / 4 大小;
  • 而对于有状态服务(常见如 IM 服务、网关接入层等零碎)新生代能够依照默认比例 1 / 3 来设置。

服务有状态,则意味著会有更多的本地缓存和会话状态信息常驻内存,应为要给老年代设置更大的空间来寄存这些对象。

step4:栈内存大小多少比拟适合?

-Xss 栈内存大小,设置单个线程栈大小,默认值和 JDK 版本、零碎无关,个别默认 512~1024kb。一个后盾服务如果常驻线程有几百个,那麽栈内存这边也会佔用了几百 M 的大小。

step5:对象年龄应该为多少才挪动到老年代比拟适合?

假如一次 minor gc 要距离二三十秒,并且,大多数对象个别在几秒内就会变为垃圾,

如果对象这么长时间都没被回收,比方 2 分钟没有回收,能够认为这些对象是会存活的比拟长的对象,从而挪动到老年代,而不是持续始终占用 survivor 区空间。

所以,能够将默认的 15 岁改小一点,比方改为 5,

那么意味着对象要通过 5 次 minor gc 才会进入老年代,整个工夫也有一两分钟了(5*30s= 150s),和几秒的工夫相比,对象曾经存活了足够长时间了。

所以:能够适当调整 JVM 参数如下:

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:SurvivorRatio=8 ‐XX:MaxTenuringThreshold=5 

step6:多大的对象,能够间接到老年代比拟适合?

对于多大的对象间接进入老年代(参数 -XX:PretenureSizeThreshold),个别能够联合本人零碎看下有没有什么大对象 生成,预估下大对象的大小,一般来说设置为 1M 就差不多了,很少有超过 1M 的大对象,

所以:能够适当调整 JVM 参数如下:

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:SurvivorRatio=8 ‐XX:MaxTenuringThreshold=5 ‐XX:PretenureSizeThreshold=1M

step7:垃圾回收器 CMS 老年代的参数优化

JDK8 默认的垃圾回收器是 -XX:+UseParallelGC(年老代)和 -XX:+UseParallelOldGC(老年代),

如果内存较大(超过 4 个 G,只是教训 值),还是倡议应用 G1.

这里是 4G 以内,又是主打“低延时”的业务零碎,能够应用上面的组合:

ParNew+CMS(-XX:+UseParNewGC -XX:+UseConcMarkSweepGC)

新生代的采纳 ParNew 回收器,工作流程就是经典复制算法,在三块区中进行流转回收,只不过采纳多线程并行的形式放慢了 MinorGC 速度。

老生代的采纳 CMS。再去 优化老年代参数:比方老年代默认在标记革除当前会做整顿,还能够在 CMS 的减少 GC 频次还是减少 GC 时长上做些取舍,

如下是响应优先的参数调优:

XX:CMSInitiatingOccupancyFraction=70

设定 CMS 在对内存占用率达到 70% 的时候开始 GC(因为 CMS 会有浮动垃圾, 所以个别都较早启动 GC)

XX:+UseCMSInitiatinpOccupancyOnly

和下面搭配应用,否则只失效一次

-XX:+AlwaysPreTouch

强制操作系统把内存真正调配给 IVM,而不是用时才调配。

综上,只有年老代参数设置正当,老年代 CMS 的参数设置根本都能够用默认值,如下所示:

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐XX:SurvivorRatio=8  ‐XX:MaxTenuringThreshold=5 ‐XX:PretenureSizeThreshold=1M ‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC ‐XX:CMSInitiatingOccupancyFraction=70 ‐XX:+UseCMSInitiatingOccupancyOnly ‐XX:+AlwaysPreTouch

参数解释

1.‐Xms3072M ‐Xmx3072M 最小最大堆设置为 3g,最大最小设置为统一避免内存抖动

2.‐Xss1M 线程栈 1m

3.‐Xmn2048M ‐XX:SurvivorRatio=8 年老代大小 2g,eden 与 survivor 的比例为 8:1:1,也就是 1.6g:0.2g:0.2g

4.-XX:MaxTenuringThreshold=5 年龄为 5 进入老年代 5.‐XX:PretenureSizeThreshold=1M 大于 1m 的大对象间接在老年代生成

6.‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC 应用 ParNew+cms 垃圾回收器组合

7.‐XX:CMSInitiatingOccupancyFraction=70 老年代中对象达到这个比例后触发 fullgc

8.‐XX:+UseCMSInitiatinpOccupancyOnly 老年代中对象达到这个比例后触发 fullgc,每次

9.‐XX:+AlwaysPreTouch 强制操作系统把内存真正调配给 IVM,而不是用时才调配。

step8:配置 OOM 时候的内存 dump 文件和 GC 日志

额定减少了 GC 日志打印、OOM 主动 dump 等配置内容,帮忙进行问题排查

-XX:+HeapDumpOnOutOfMemoryError

在 Out Of Memory,JVM 快死掉的时候,输入 Heap Dump 到指定文件。

不然开发很多时候还真不知道怎么重现谬误。

门路只指向目录,JVM 会放弃文件名的唯一性,叫 java_pid${pid}.hprof。

-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=${LOGDIR}/

因为如果指向特定的文件,而文件已存在,反而不能写入。

输入 4G 的 HeapDump,会导致 IO 性能问题,在一般硬盘上,会造成 20 秒以上的硬盘 IO 跑满,

须要留神一下,但在容器环境下,这个也会影响同一宿主机上的其余容器。

GC 的日志的输入也很重要:

-Xloggc:/dev/xxx/gc.log 
-XX:+PrintGCDateStamps 
-XX:+PrintGCDetails

GC 的日志实际上对系统性能影响不大,打日志对排查 GC 问题很重要。

一份通用的 JVM 参数模板

一般来说,大企业或者架构师团队,都会为我的项目的业务零碎定制一份较为通用的 JVM 参数模板,然而许多小企业和团队可能就疏于这一块的设计,如果老板某一天忽然让你负责定制一个新零碎的 JVM 参数,你上网去搜大量的 JVM 调优文章或博客,后果发现都是零零散散的、不成体系的 JVM 参数解说,基本下不了手,这个时候你就须要一份较为通用的 JVM 参数模板了,不能保障性能最佳,然而至多能让 JVM 这一层是稳固可控的,

在这里给大家总结了一份模板:

基于 4C8G 零碎的 ParNew+CMS 回收器模板(响应优先),新生代大小依据业务灵便调整!

-Xms4g
-Xmx4g
-Xmn2g
-Xss1m
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=10
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+AlwaysPreTouch
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:gc.log

如果是 GC 的吞吐优先,举荐应用 G1,基于 8C16G 零碎的 G1 回收器模板:

G1 收集器本身曾经有一套预测和调整机制了,因而咱们首先的抉择是置信它,

即调整 -XX:MaxGCPauseMillis=N参数,这也合乎 G1 的目标——让 GC 调优尽量简略!

同时也不要本人显式设置新生代的大小(用 -Xmn 或 -XX:NewRatio 参数),

如果人为干涉新生代的大小,会导致指标工夫这个参数生效。

-Xms8g
-Xmx8g
-Xss1m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150
-XX:InitiatingHeapOccupancyPercent=40
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
G1 参数 形容 默认值
XX:MaxGCPauseMillis=N 最大 GC 进展工夫。柔性指标,JVM 满足 90%,不保障 100%。 200
-XX:nitiatingHeapOccupancyPercent=n 当整个堆的空间应用百分比超过这个值时,就会融发 MixGC 45

针对 -XX:MaxGCPauseMillis 来说,参数的设置带有显著的倾向性:调低↓:提早更低,但 MinorGC 频繁,MixGC 回收老年代区缩小,增大 Full GC 的危险。调高↑:单次回收更多的对象,但零碎整体响应工夫也会被拉长。

针对 InitiatingHeapOccupancyPercent 来说,调参大小的成果也不一样:调低↓:更早触发 MixGC,节约 cpu。调高↑:沉积过多代回收 region,增大 FullGC 的危险。

调优总结

零碎在上线前的综合调优思路:

1、业务预估:依据预期的并发量、均匀每个工作的内存需要大小,而后评估须要几台机器来承载,每台机器须要什么样的配置。

2、容量预估:依据零碎的工作处理速度,而后正当调配 Eden、Surivior 区大小,老年代的内存大小。

3、回收器选型:响应优先的零碎,倡议采纳 ParNew+CMS 回收器;吞吐优先、多核大内存 (heap size≥8G) 服务,倡议采纳 G1 回收器。

4、优化思路:让长寿对象在 MinorGC 阶段就被回收(同时回收后的存活对象 <Survivor 区域 50%,可管制保留在新生代),长命对象尽早进入老年代,不要在新生代来回复制;尽量减少 Full GC 的频率,防止 FGC 零碎的影响。

5、到目前为止,总结到的调优的过程次要基于上线前的测试验证阶段,所以咱们尽量在上线之前,就将机器的 JVM 参数设置到最优!

JVM 调优只是一个伎俩,但并不一定所有问题都能够通过 JVM 进行调优解决,大多数的 Java 利用不须要进行 JVM 优化,咱们能够遵循以下的一些准则:

  • 上线之前,应先思考将机器的 JVM 参数设置到最优;
  • 缩小创建对象的数量(代码层面);
  • 缩小应用全局变量和大对象(代码层面);
  • 优先架构调优和代码调优,JVM 优化是不得已的伎俩(代码、架构层面);
  • 剖析 GC 状况优化代码比优化 JVM 参数更好(代码层面);

通过以上准则,咱们发现,其实最无效的优化伎俩是架构和代码层面的优化,而 JVM 优化则是最初不得已的伎俩,也能够说是对服务器配置的最初一次“压迫”。

什么是 ZGC?

ZGC(Z Garbage Collector)是一款由 Oracle 公司研发的,以低提早为首要指标的一款垃圾收集器。

它是基于动静 Region 内存布局,(临时)不设年龄分代,应用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记 - 整顿算法的收集器。

在 JDK 11 新退出,还在试验阶段,

次要特点是:回收 TB 级内存(最大 4T),进展工夫不超过 10ms。

长处:低进展,高吞吐量,ZGC 收集过程中额定消耗的内存小

毛病:浮动垃圾

目前应用的非常少,真正遍及还是须要写工夫的。

如何抉择垃圾收集器?

在实在场景中应该如何去抉择呢,上面给出几种倡议,心愿对你有帮忙:

1、如果你的堆大小不是很大(比方 100MB),抉择串行收集器个别是效率最高的。参数:-XX:+UseSerialGC

2、如果你的利用运行在单核的机器上,或者你的虚拟机核数只有 单核,抉择串行收集器仍然是适合的,这时候启用一些并行收集器没有任何收益。参数:-XX:+UseSerialGC

3、如果你的利用是“吞吐量”优先的,并且对较长时间的进展没有什么特地的要求。抉择并行收集器是比拟好的。参数:-XX:+UseParallelGC

4、如果你的利用对响应工夫要求较高,想要较少的进展。甚至 1 秒的进展都会引起大量的申请失败,那么抉择 G1、ZGC、CMS 都是正当的。尽管这些收集器的 GC 进展通常都比拟短,但它须要一些额定的资源去解决这些工作,通常吞吐量会低一些。参数:-XX:+UseConcMarkSweepGC-XX:+UseG1GC-XX:+UseZGC 等。从下面这些出发点来看,咱们平时的 Web 服务器,都是对响应性要求十分高的。

选择性其实就集中在 CMS、G1、ZGC 上。而对于某些定时工作,应用并行收集器,是一个比拟好的抉择。

Hotspot 为什么应用元空间替换了永恒代?

什么是元空间?什么是永恒代?为什么用元空间代替永恒代?

咱们先回顾一下 办法区 吧, 看看虚拟机运行时数据内存图,如下:

办法区和堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、动态变量、即时编译后的代码等数据。

什么是永恒代?它和办法区有什么关系呢?

如果在 HotSpot 虚拟机上开发、部署,很多程序员都把办法区称作永恒代。

能够说办法区是标准,永恒代是 Hotspot 针对该标准进行的实现。

在 Java7 及以前的版本,办法区都是永恒代实现的。

什么是元空间?它和办法区有什么关系呢?

对于 Java8,HotSpots 勾销了永恒代,取而代之的是元空间(Metaspace)。

换句话说,就是办法区还是在的,只是实现变了,从永恒代变为元空间了。

为什么应用元空间替换了永恒代?

永恒代的办法区,和堆应用的物理内存是间断的。

永恒代 是通过以下这两个参数配置大小的~

  • -XX:PremSize:设置永恒代的初始大小
  • -XX:MaxPermSize: 设置永恒代的最大值,默认是 64M

对于 永恒代,如果动静生成很多 class 的话,就很可能呈现java.lang.OutOfMemoryError:PermGen space 谬误,因为永恒代空间配置无限嘛。最典型的场景是,在 web 开发比拟多 jsp 页面的时候。

JDK8 之后,办法区存在于元空间(Metaspace)。

物理内存不再与堆间断,而是间接存在于本地内存中,实践上机器 内存有多大,元空间就有多大

能够通过以下的参数来设置元空间的大小:

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时 GC 会对该值进行调整:如果开释了大量的空间,就适当升高该值;如果开释了很少的空间,那么在不超过 MaxMetaspaceSize 时,适当进步该值。
  • -XX:MaxMetaspaceSize,最大空间,默认是没有限度的。
  • -XX:MinMetaspaceFreeRatio,在 GC 之后,最小的 Metaspace 残余空间容量的百分比,缩小为调配空间所导致的垃圾收集
  • -XX:MaxMetaspaceFreeRatio,在 GC 之后,最大的 Metaspace 残余空间容量的百分比,缩小为开释空间所导致的垃圾收集

所以,为什么应用元空间替换永恒代?

外表上看是为了防止 OOM 异样。

因为通常应用 PermSize 和 MaxPermSize 设置永恒代的大小就决定了永恒代的下限,然而不是总能晓得应该设置为多大适合, 如果应用默认值很容易遇到 OOM 谬误。

当应用元空间时,能够加载多少类的元数据就不再由 MaxPermSize 管制, 而由零碎的理论可用空间来管制啦。

什么是 Stop The World ? 什么是 OopMap?什么是平安点?

进行垃圾回收的过程中,会波及对象的挪动。

为了保障对象援用更新的正确性,必须暂停所有的用户线程,像这样的进展,虚拟机设计者形象形容为Stop The World。也简称为 STW。

在 HotSpot 中,有个数据结构(映射表)称为OopMap

一旦类加载动作实现的时候,HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来,记录到 OopMap。

在即时编译过程中,也会在 特定的地位 生成 OopMap,记录下栈上和寄存器里哪些地位是援用。

这些特定的地位次要在:1. 循环的开端(非 counted 循环)

2. 办法临返回前 / 调用办法的 call 指令后

3. 可能抛异样的地位

这些地位就叫作 平安点(safepoint)。

用户程序执行时并非在代码指令流的任意地位都可能在停顿下来开始垃圾收集,而是必须是执行到平安点才可能暂停。

最初说一句(别白嫖,求关注)

陈某每一篇文章都是精心输入,如果这篇文章对你有所帮忙,或者有所启发的话,帮忙 点赞 在看 转发 珍藏,你的反对就是我坚持下去的最大能源!

关注公众号:【码猿技术专栏】,公众号内有超赞的粉丝福利,回复:加群,能够退出技术探讨群,和大家一起探讨技术,吹牛逼!

退出移动版