GC垃圾回收器
寻找垃圾算法引用计数法
引用计数法(ReferenceCount)会给对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器的值就1,当引用失效时,计数器值就1,计数器的值为0的对象不可能在被使用,这个时候就可以判定这个对象是垃圾。
当图中的数值变成0时,这个时候使用引用计数算法就可以判定它是垃圾了,但是引用计数法不能解决一个问题,就是当对象是循环引用的时候,计数器值都不为0,这个时候引用计数器无法通知GC收集器来回收他们,如下图所示:
这个时候就需要使用到我们的根可达算法。可达性分析
根可达算法(RootSearching)的意思是说从根上开始搜索,当一个程序启动后,马上需要的那些个对象就叫做根对象,所谓的根可达算法就是首先找到根对象,然后跟着这根线一直往外找到那些有用的。常见的GCroots如下:线程栈变量:线程里面会有线程栈和main栈帧,从这个main()里面开始的这些对象都是我们的根对象静态变量:一个class它有一个静态的变量,load到内存之后马上就得对静态变量进行初始化,所以静态变量到的对象这个叫做根对象常量池:如果你这个class会用到其他的class的那些个类的对象,这些就是根对象JNI:如果我们调用了C和C写的那些本地方法所用到的那些个类或者对象
图中的object5和object6虽然他们之间互相引用了,但是从根找不到它,所以就是垃圾,而object8没有任何引用自然而然也是垃圾,其他的Object对象都有可以从根找到的,所以是有用的,不会被垃圾回收掉。
GCRoot
GCRoots是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。GCRoots包括:Java线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用所有当前被加载的Java类Java类的引用类型静态变量运行时常量池里的引用类型常量(String或Class类型)JVM内部数据结构的一些引用,比如sun。jvm。hotspot。memory。Universe类用于同步的监控对象,比如调用了对象的wait()方法JNIhandles,包括globalhandles和localhandles
GCRoots大体可以分为三大类:活动线程相关的各种引用类的静态变量的引用JNI引用清理垃圾算法
清理垃圾算法又叫内存回收算法。标记(Mark)
垃圾回收的第一步,就是找出活跃的对象。根据GCRoots遍历所有的可达对象,这个过程,就叫作标记。
如图所示,圆圈代表的是对象。绿色的代表GCRoots,红色的代表可以追溯到的对象。可以看到标记之后,仍然有多个灰色的圆圈,它们都是被回收的对象。清除(Sweep)
清除阶段就是把未被标记的对象回收掉。
但是这种简单的清除方式,有一个明显的弊端,那就是碎片问题。比如我申请了1k、2k、3k、4k、5k的内存。
由于某种原因,2k和4k的内存,我不再使用,就需要交给垃圾回收器回收。
这个时候,我应该有足足6k的空闲空间。接下来,我打算申请另外一个5k的空间,结果系统告诉我内存不足了。系统运行时间越长,这种碎片就越多。在很久之前使用Windows系统时,有一个非常有用的功能,就是内存整理和磁盘整理,运行之后有可能会显著提高系统性能。这个出发点是一样的。复制(Copying)
优点因为是对整个半区进行内存回收,内存分配时不用考虑内存碎片等情况。实现简单,效率较高
不足之处既然要复制,需要提前预留内存空间,有一定的浪费在对象存活率较高时,需要复制的对象较多,效率将会变低整理(Compact)
其实,不用分配一个对等的额外空间,也是可以完成内存的整理工作。可以把内存想象成一个非常大的数组,根据随机的index删除了一些数据。那么对整个数组的清理,其实是不需要另外一个数组来进行支持的,使用程序就可以实现。它的主要思路,就是移动所有存活的对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部回收。
但是需要注意,这只是一个理想状态。对象的引用关系一般都是非常复杂的,我们这里不对具体的算法进行描述。你只需要了解,从效率上来说,一般整理算法是要低于复制算法的。扩展回收算法
目前JVM的垃圾回收器都是对几种朴素算法的发扬光大(没有最优的算法,只有最合适的算法):复制算法(Copying):复制算法是所有算法里面效率最高的,缺点是会造成一定的空间浪费标记清除(MarkSweep):效率一般,缺点是会造成内存碎片问题标记整理(MarkCompact):效率比前两者要差,但没有空间浪费,也消除了内存碎片问题
标记清除(MarkSweep)
首先从GCRoot开始遍历对象图,并标记(Mark)所遇到的每个对象,标识出所有要回收的对象。然后回收器检查堆中每一个对象,并将所有未被标记的对象进行回收。
不足之处标记、清除的效率都不高清除后产生大量的内存碎片,空间碎片太多会导致在分配大对象时无法找到足够大的连续内存,从而不得不触发另一次垃圾回收动作标记整理(MarkCompact)
与标记清除算法类似,但不是在标记完成后对可回收对象进行清理,而是将所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
优点消除了标记清除导致的内存分散问题,也消除了复制算法中内存减半的高额代价
不足之处效率低下,需要标记所有存活对象,还要标记所有存活对象的引用地址。效率上低于复制算法标记复制(MarkCoping)
标记复制算法将内存分为两块相同大小的区域(比如新生代的Survivor区),每次在其中一块区域分配元素,当这块区域内存占满时,就会将存活下来的元素复制到另一块内存区域并清空当前内存区域。缺点:浪费一半的内存空间优点:简单高效
JVM在Eden区保存新对象,在GC时,将Eden和Survivor中存活对象复制到Survivor的另一个分区。这是JVM对复制算法的一个优化。只浪费了110的内存空间【JVM的Eden区和Survivor区的比例为8:2】
分代收集(GenerationalCollection)
分代收集就是根据对象的存活周期将内存分为新生代和老年代。新生代对象朝生夕死,每次收集都有大量对象(99)死去,所以可以选择标记复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集老年代对象生存几率比较高,存活对象比较多,如果选择复制算法需要付出较高的IO成本,而且没用额外的空间可以用于复制,此时选择标记清除或者标记整理就比较合理
研究表明大部分对象可以分为两类:大部分对象的生命周期都很短其他对象则很可能会存活很长时间
根据对象存活周期的不同将内存划分为几块。对不同周期的对象采取不同的收集算法:新生代:每次垃圾收集会有大批对象回收,所以采取复制算法老年代:对象存活率高,采取标记清理或者标记整理算法
年轻代(YoungGeneration)
年轻代使用的垃圾回收算法是复制算法。因为年轻代发生GC后,只会有非常少的对象存活,复制这部分对象是非常高效的。但复制算法会造成一定的空间浪费,所以年轻代中间也会分很多区域。
如图所示,年轻代分为:1个伊甸园空间(Eden),2个幸存者空间(Survivor)。当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(MinorGC)。具体过程如下:在Eden区执行了第一次GC之后,存活的对象会被移动到其中一个Survivor分区(以下简称from)Eden区再次GC,这时会采用复制算法,将Eden和from区一起清理。存活的对象会被复制到to区,然后只需要清空from区就可以了
在这个过程中,总会有1个Survivor分区是空置的。Eden、from、to的默认比例是8:1:1,所以只会造成10的空间浪费。这个比例,是由参数XX:SurvivorRatio进行配置的(默认为8)。
老年代(OldTenuredGeneration)
老年代一般使用标记清除、标记整理算法,因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,还不如采取就地收集的方式。对象进入老年代的途径如下:提升(Promotion)如果对象够老,会通过提升进入老年代分配担保年轻代回收后存活的对象大于10时,因Survivor空间不够存储,对象就会直接在老年代上分配大对象直接在老年代分配超出某个大小的对象将直接在老年代分配动态对象年龄判定有的垃圾回收算法,并不要求age必须达到15才能晋升到老年代,它会使用一些动态的计算方法。比如,如果幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或等于age的对象将会直接进入老年代。分区收集GC垃圾收集器
GC垃圾收集器的JVM配置参数:XX:UseSerialGC:年轻代和老年代都用串行收集器XX:UseParNewGC:年轻代使用ParNew,老年代使用SerialOldXX:UseParallelGC:年轻代使用ParallerGC,老年代使用SerialOldXX:UseParallelOldGC:新生代和老年代都使用并行收集器XX:UseConcMarkSweepGC:表示年轻代使用ParNew,老年代的用CMSXX:UseG1GC:使用G1垃圾回收器XX:UseZGC:使用ZGC垃圾回收器年轻代收集器Serial收集器
处理GC的只有一条线程,并且在垃圾回收的过程中暂停一切用户线程。最简单的垃圾回收器,但千万别以为它没有用武之地。因为简单,所以高效,它通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。
ParNew收集器
ParNew是Serial的多线程版本。由多条GC线程并行地进行垃圾清理。清理过程依然要停止用户线程。ParNew追求低停顿时间,与Serial唯一区别就是使用了多线程进行垃圾收集,在多CPU环境下性能比Serial会有一定程度的提升;但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial。
ParallelScavenge收集器
另一个多线程版本的垃圾回收器。它与ParNew的主要区别是:ParallelScavenge:追求CPU吞吐量,能够在较短时间完成任务,适合没有交互的后台计算。弱交互强计算ParNew:追求降低用户停顿时间,适合交互式应用。强交互弱计算老年代收集器SerialOld收集器
与年轻代的Serial垃圾收集器对应,都是单线程版本,同样适合客户端使用。年轻代的Serial,使用复制算法。老年代的OldSerial,使用标记整理算法。
ParallelOld收集器
ParallelOld收集器是ParallelScavenge的老年代版本,追求CPU吞吐量。
CMS收集器
并发标记清除(ConcurrentMarkSweep,CMS)垃圾回收器,是一款致力于获取最短停顿时间的收集器,使用多个线程来扫描堆内存并标记可被清除的对象,然后清除标记的对象。在下面两种情形下会暂停工作线程:在老年代中标记引用对象的时候在做垃圾回收的过程中堆内存中有变化发生
对比与并行垃圾回收器,CMS回收器使用更多的CPU来保证更高的吞吐量。如果我们可以有更多的CPU用来提升性能,那么CMS垃圾回收器是比并行回收器更好的选择。使用XX:UseParNewGCJVM参数来开启使用CMS垃圾回收器。
主要流程如下:初始标记(CMSinitialmark):仅标记出GCRoots能直接关联到的对象。需要Stoptheworld并发标记(CMSconcurrenrmark):进行GCRoots遍历的过程,寻找出所有可达对象重新标记(CMSremark):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。需要Stoptheworld并发清除(CMSconcurrentsweep):清出垃圾
CMS触发机制:当老年代的使用率达到80时,就会触发一次CMSGC。XX:CMSInitiatingOccupancyFraction80XX:UseCMSInitiatingOccupancyOnly
优点并发收集停顿时间最短
缺点并发回收导致CPU资源紧张
在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低程序总吞吐量。CMS默认启动的回收线程数是:(CPU核数3)4,当CPU核数不足四个时,CMS对用户程序的影响就可能变得很大。无法清理浮动垃圾
在CMS的并发标记和并发清理阶段,用户线程还在继续运行,就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留到下一次垃圾收集时再清理掉。这一部分垃圾称为浮动垃圾。并发失败(ConcurrentModeFailure)
由于在垃圾回收阶段用户线程还在并发运行,那就还需要预留足够的内存空间提供给用户线程使用,因此CMS不能像其他回收器那样等到老年代几乎完全被填满了再进行回收,必须预留一部分空间供并发回收时的程序运行使用。默认情况下,当老年代使用了80的空间后就会触发CMS垃圾回收,这个值可以通过XXCMSInitiatingOccupancyFraction参数来设置。
这里会有一个风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次并发失败(ConcurrentModeFailure),这时候虚拟机将不得不启动后备预案:StopTheWorld,临时启用SerialOld来重新进行老年代的垃圾回收,这样一来停顿时间就很长了。内存碎片问题
CMS是一款基于标记清除算法实现的回收器,这意味着回收结束时会有内存碎片产生。内存碎片过多时,将会给大对象分配带来麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次FullGC的情况。
为了解决这个问题,CMS收集器提供了一个XX:UseCMSCompactAtFullCollection开关参数(默认开启),用于在FullGC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,是无法并发的,这样停顿时间就会变长。还有另外一个参数XX:CMSFullGCsBeforeCompaction,这个参数的作用是要求CMS在执行过若干次不整理空间的FullGC之后,下一次进入FullGC前会先进行碎片整理(默认值为0,表示每次进入FullGC时都进行碎片整理)。
作用内存区域:老年代
适用场景:对停顿时间敏感的场合
算法类型:标记清除新生代和老年代收集G1收集器
G1(GarbageFirst)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器。G1设计初衷就是替换CMS,成为一种全功能收集器。G1在JDK9之后成为服务端模式下的默认垃圾回收器,取代了ParallelScavenge加ParallelOld的默认组合,而CMS被声明为不推荐使用的垃圾回收器。G1从整体来看是基于标记整理算法实现的回收器,但从局部(两个Region之间)上看又是基于标记复制算法实现的。G1回收过程,G1回收器的运作过程大致可分为四个步骤:初始标记(会STW):仅仅只是标记一下GCRoots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行MinorGC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。并发标记:从GCRoots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象。最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。清理阶段(会STW):更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的。
G1收集器中的堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存,结构如下:
每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous(巨大的),这表示这些Region存储的是巨型对象(humongousobject,Hobj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。
Region
堆内存中一个Region的大小可以通过XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方。如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实际大小,默认把堆内存按照2048份均分,最后得到一个合理的大小。
GC模式
younggc发生在年轻代的GC算法,一般对象(除了巨型对象)都是在edenregion中分配内存,当所有edenregion被耗尽无法申请内存时,就会触发一次younggc,这种触发机制和之前的younggc差不多,执行完一次younggc,活跃对象会被拷贝到survivorregion或者晋升到oldregion中,空闲的region会被放入空闲列表中,等待下次被使用。XX:MaxGCPauseMillis:设置G1收集过程目标时间,默认值200msXX:G1NewSizePercent:新生代最小值,默认值5XX:G1MaxNewSizePercent:新生代最大值,默认值60
mixedgc
当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixedgc,该算法并不是一个oldgc,除了回收整个youngregion,还会回收一部分的oldregion,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些oldregion进行收集,从而可以对垃圾回收的耗时时间进行控制
fullgc
如果对象内存分配速度过快,mixedgc来不及回收,导致老年代被填满,就会触发一次fullgc,G1的fullgc算法就是单线程执行的serialoldgc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免fullgc
G1垃圾回收器应用于大的堆内存空间。它将堆内存空间划分为不同的区域,对各个区域并行地做回收工作。G1在回收内存空间后还立即对空闲空间做整合工作以减少碎片。CMS却是在全部停止(stoptheworld,STW)时执行内存整合工作。对于不同的区域G1根据垃圾的数量决定优先级。使用XX:UseG1GCJVM参数来开启使用G1垃圾回收器。
主要流程如下:初始标记(InitialMarking):标记从GCRoot可达的对象。会发生STW并发标记(ConcurrenrMarking):标记出GCRoot可达对象衍生出去的存活对象,并收集各个Region的存活对象信息。整个过程gccollector线程与应用线程可以并行执行最终标记(FinalMarking):标记出在并发标记过程中遗漏的,或内部引用发生变化的对象。会发生STW筛选回收(LiveDataCountingAndEvacution):垃圾清除过程,如果发现一个Region中没有存活对象,则把该Region加入到空闲列表中
XX:InitiatingHeapOccupancyPercent:当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixedgc。
优点:并行与并发,充分发挥多核优势分代收集,所以不需要与其它收集器配合即可工作空间整合,整体来看基于标记整理算法,局部采用复制算法都不会产生内存碎片可以指定GC最大停顿时长
缺点:需要记忆集来记录新生代和老年代之间的引用关系需要占用大量的内存,可能达到整个堆内存容量的20甚至更多
作用内存区域:跨代
适用场景:作为关注停顿时间的场景的收集器备选方案
算法类型:整体来看基于标记整理算法,局部采用复制算法ZGC收集器
ZGarbageCollector,简称ZGC,是JDK11中新加入的尚在实验阶段的低延迟垃圾收集器。它和Shenandoah同属于超低延迟的垃圾收集器,但在吞吐量上比Shenandoah有更优秀的表现,甚至超过了G1,接近了吞吐量优先的Parallel收集器组合,可以说近乎实现了鱼与熊掌兼得。
与CMS中的ParNew和G1类似,ZGC也采用标记复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。ZGC垃圾回收周期如下图所示:
ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GCRoots,其处理时间和GCRoots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GCRoots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。
ZGC的内存布局
与Shenandoah和G1一样,ZGC也采用基于Region的堆内存布局,但与它们不同的是,ZGC的Region具有动态性,也就是可以动态创建和销毁,容量大小也是动态的,有大、中、小三类容量:
小型Region(SmallRegion):容量固定为2MB,用于放置小于256KB的小对象中型Region(MediumRegion):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象大型Region(LargeRegion):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作大型Region,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB
在JDK11及以上版本,可以通过以下参数开启ZGC:XX:UnlockExperimentalVMOptionsXX:UseZGC。Shenandoah收集器
Shenandoah与G1有很多相似之处,比如都是基于Region的内存布局,都有用于存放大对象的HumongousRegion,默认回收策略也是优先处理回收价值最大的Region。不过也有三个重大的区别:Shenandoah支持并发的整理算法,G1整理阶段虽是多线程并行,但无法与用户程序并发执行默认不使用分代收集理论使用连接矩阵(ConnectionMatrix)记录跨Region的引用关系,替换掉了G1中的记忆级(RememberedSet),内存和计算成本更低
Shenandoah收集器的工作原理相比G1要复杂不少,其运行流程示意图如下:
可见Shenandoah的并发程度明显比G1更高,只需要在初始标记、最终标记、初始引用更新和最终引用更新这几个阶段进行短暂的StopTheWorld,其他阶段皆可与用户程序并发执行,其中最重要的并发标记、并发回收和并发引用更新详情如下:并发标记(ConcurrentMarking)并发回收(ConcurrentEvacuation)并发引用更新(ConcurrentUpdateReference)
Shenandoah的高并发度让它实现了超低的停顿时间,但是更高的复杂度也伴随着更高的系统开销,这在一定程度上会影响吞吐量,下图是Shenandoah与之前各种收集器在停顿时间维度和系统开销维度上的对比:
OracleJDK并不支持Shenandoah,如果你用的是OpenJDK12或某些支持Shenandoah移植版的JDK的话,可以通过以下参数开启Shenandoah:XX:UnlockExperimentalVMOptionsXX:UseShenandoahGC。GC日志日志格式
ParallelGCYoungGC日志
ParallelGCFullGC日志
最佳实践
在不同的JVM的不垃圾回收器上,看参数默认是什么,不要轻信别人的建议,命令行示例如下:javaXX:PrintFlagsFinalXX:UseG1GC21grepUseAdaptiveSizePolicy
PrintCommandLineFlags:通过它,你能够查看当前所使用的垃圾回收器和一些默认的值。javaXX:PrintCommandLineFlagsversionXX:InitialHeapSize127905216XX:MaxHeapSize2046483456XX:PrintCommandLineFlagsXX:UseCompressedClassPointersXX:UseCompressedOopsXX:UseParallelGCopenjdkversion1。8。041OpenJDKRuntimeEnvironment(build1。8。041b04)OpenJDK64BitServerVM(build25。40b25,mixedmode)
G1垃圾收集器JVM参数最佳实践:1。基本参数server服务器模式Xmx12g初始堆大小Xms12g最大堆大小Xss256k每个线程的栈内存大小XX:UseG1GC使用G1(GarbageFirst)垃圾收集器XX:MetaspaceSize256m元空间初始大小XX:MaxMetaspaceSize1g元空间最大大小XX:MaxGCPauseMillis200每次YGCMixedGC的最多停顿时间(期望最长停顿时间)2。必备参数XX:PrintGCDetails输出详细GC日志XX:PrintGCDateStamps输出GC的时间戳(以日期的形式,如20130504T21:53:59。2340800)XX:PrintTenuringDistribution打印对象分布:为了分析GC时的晋升情况和晋升导致的高暂停,看对象年龄分布日志XX:PrintHeapAtGC在进行GC的前后打印出堆的信息XX:PrintReferenceGC打印Reference处理信息:强引用弱引用软引用虚引用finalize方法万一有问题XX:PrintGCApplicationStoppedTime打印STW时间XX:PrintGCApplicationConCurrentTime打印GC间隔的服务运行时长3。日志分割参数XX:UseGCLogFileRotation开启日志文件分割XX:NumberOfGCLogFiles14最多分割几个文件,超过之后从头文件开始写XX:GCLogFileSize32M每个文件上限大小,超过就触发分割Xloggc:pathtogct。logGC日志输出的文件路径,使用t作为日志文件名,即gc20210329204147。log
CMS垃圾收集器JVM参数最佳实践:1。基本参数server服务器模式Xmx4gJVM最大允许分配的堆内存,按需分配Xms4gJVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存Xmn256m年轻代内存大小,整个JVM内存年轻代年老代持久代Xss512k设置每个线程的堆栈大小XX:DisableExplicitGC忽略手动调用GC,System。gc()的调用就会变成一个空调用,完全不触发GCXX:UseConcMarkSweepGC使用CMS垃圾收集器XX:CMSParallelRemarkEnabled降低标记停顿XX:UseCMSCompactAtFullCollection在FULLGC的时候对年老代的压缩XX:UseFastAccessorMethods原始类型的快速优化XX:UseCMSInitiatingOccupancyOnly使用手动定义初始化定义开始CMS收集XX:LargePageSizeInBytes128m内存页的大小XX:CMSInitiatingOccupancyFraction70使用cms作为垃圾回收使用70后开始CMS收集2。必备参数XX:PrintGCDetails输出详细GC日志XX:PrintGCDateStamps输出GC的时间戳(以日期的形式,如20130504T21:53:59。2340800)XX:PrintTenuringDistribution打印对象分布:为分析GC时的晋升情况和晋升导致的高暂停,看对象年龄分布XX:PrintHeapAtGC在进行GC的前后打印出堆的信息XX:PrintReferenceGC打印Reference处理信息:强引用弱引用软引用虚引用finalize方法万一有问题XX:PrintGCApplicationStoppedTime打印STW时间XX:PrintGCApplicationConCurrentTime打印GC间隔的服务运行时长3。日志分割参数XX:UseGCLogFileRotation开启日志文件分割XX:NumberOfGCLogFiles14最多分割几个文件,超过之后从头文件开始写XX:GCLogFileSize32M每个文件上限大小,超过就触发分割Xloggc:pathtogct。logGC日志输出的文件路径,使用t作为日志文件名,即gc20210329204147。log
test、stage环境jvm使用CMS参数配置(jdk8)serverXms256MXmx256MXss512kXmn96MXX:MetaspaceSize128MXX:MaxMetaspaceSize128MXX:InitialHeapSize256MXX:MaxHeapSize256MXX:PrintCommandLineFlagsXX:UseConcMarkSweepGCXX:UseCMSInitiatingOccupancyOnlyXX:CMSInitiatingOccupancyFraction80XX:CMSClassUnloadingEnabledXX:CMSParallelRemarkEnabledXX:CMSScavengeBeforeRemarkXX:UseCMSCompactAtFullCollectionXX:CMSFullGCsBeforeCompaction2XX:CMSParallelInitialMarkEnabledXX:CMSParallelRemarkEnabledXX:UnlockDiagnosticVMOptionsXX:ParallelRefProcEnabledXX:AlwaysPreTouchXX:MaxTenuringThreshold8XX:UseCompressedClassPointersXX:UseCompressedOopsXX:PrintGCXX:PrintGCApplicationConcurrentTimeXX:PrintGCApplicationStoppedTimeXX:PrintGCDateStampsXX:PrintGCDetailsXX:PrintGCTimeStampsXX:PrintHeapAtGCXX:PrintTenuringDistributionXX:SurvivorRatio8Xloggc:。。logsgc。logXX:HeapDumpOnOutOfMemoryErrorXX:HeapDumpPath。。dump
online环境jvm使用CMS参数配置(jdk8)serverXms4GXmx4GXss512kXmn1536MXX:MetaspaceSize128MXX:MaxMetaspaceSize128MXX:InitialHeapSize4GXX:MaxHeapSize4GXX:PrintCommandLineFlagsXX:UseConcMarkSweepGCXX:UseCMSInitiatingOccupancyOnlyXX:CMSInitiatingOccupancyFraction80XX:CMSClassUnloadingEnabledXX:CMSParallelRemarkEnabledXX:CMSScavengeBeforeRemarkXX:UseCMSCompactAtFullCollectionXX:CMSFullGCsBeforeCompaction2XX:CMSParallelInitialMarkEnabledXX:CMSParallelRemarkEnabledXX:UnlockDiagnosticVMOptionsXX:ParallelRefProcEnabledXX:AlwaysPreTouchXX:MaxTenuringThreshold10XX:UseCompressedClassPointersXX:UseCompressedOopsXX:PrintGCXX:PrintGCApplicationConcurrentTimeXX:PrintGCApplicationStoppedTimeXX:PrintGCDateStampsXX:PrintGCDetailsXX:PrintGCTimeStampsXX:PrintHeapAtGCXX:PrintTenuringDistributionXX:SurvivorRatio8Xloggc:。。logsgc。logXX:HeapDumpOnOutOfMemoryErrorXX:HeapDumpPath。。dumpGC场景FullGC场景
场景一:System。gc()方法的调用
此方法的调用是建议JVM进行FullGC,虽然只是建议而非一定,但很多情况下它会触发FullGC,从而增加FullGC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过XX:DisableExplicitGC来禁止RMI调用System。gc()。
场景二:老年代代空间不足原因分析:新生代对象转入老年代、创建大对象或数组时,执行FullGC后仍空间不足抛出错误:Java。lang。OutOfMemoryError:Javaheapspace解决办法:尽量让对象在YoungGC时被回收让对象在新生代多存活一段时间不要创建过大的对象或数组
场景三:永生区空间不足原因分析:JVM方法区因系统中要加载的类、反射的类和调用的方法较多而可能会被占满抛出错误:java。lang。OutOfMemoryError:PermGenspace解决办法:增大老年代空间大小使用CMSGC
场景四:CMSGC时出现promotionfailed和concurrentmodefailure原因分析:promotionfailed:是在进行MinorGC时,survivorspace放不下、对象只能放入老年代,而此时老年代也放不下造成concurrentmodefailure:是在执行CMSGC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的抛出错误:GC日志中存在promotionfailed和concurrentmode解决办法:增大幸存区或老年代
场景五:堆中分配很大的对象原因分析:创建大对象或长数据时,此对象直接进入老年代,而老年代虽有很大剩余空间,但没有足够的连续空间来存储抛出错误:触发FullGC解决办法:配置XX:UseCMSCompactAtFullCollection开关参数,用于享受用完FullGC后额外免费赠送的碎片整理过程,但同时停顿时间不得不变长。可以使用XX:CMSFullGCsBeforeCompaction参数来指定执行多少次不压缩的FullGC后才执行一次压缩CMSGC场景
场景一:动态扩容引起的空间震荡现象
服务刚刚启动时GC次数较多,最大空间剩余很多但是依然发生GC,这种情况我们可以通过观察GC日志或者通过监控工具来观察堆的空间变化情况即可。GCCause一般为AllocationFailure,且在GC日志中会观察到经历一次GC,堆内各个空间的大小会被调整,如下图所示:
原因
在JVM的参数中Xms和Xmx设置的不一致,在初始化时只会初始Xms大小的空间存储信息,每当空间不够用时再向操作系统申请,这样的话必然要进行一次GC。另外,如果空间剩余很多时也会进行缩容操作,JVM通过XX:MinHeapFreeRatio和XX:MaxHeapFreeRatio来控制扩容和缩容的比例,调节这两个值也可以控制伸缩的时机。整个伸缩的模型理解可以看这个图,当committed的空间大小超过了低水位高水位的大小,capacity也会随之调整:
策略
观察CMSGC触发时间点OldMetaSpace区的committed占比是不是一个固定的值,或者像上文提到的观察总的内存使用率也可以。尽量将成对出现的空间大小配置参数设置成固定的,如Xms和Xmx,XX:MaxNewSize和XX:NewSize,XX:MetaSpaceSize和XX:MaxMetaSpaceSize等。
场景二:显式GC的去与留现象
除了扩容缩容会触发CMSGC之外,还有Old区达到回收阈值、MetaSpace空间不足、Young区晋升失败、大对象担保失败等几种触发条件,如果这些情况都没有发生却触发了GC,这种情况有可能是代码中手动调用了System。gc方法,此时可以找到GC日志中的GCCause确认下。原因
保留System。gc:CMS中使用ForegroundCollector时将会带来非常长的STW,在应用程序中System。gc被频繁调用,那就非常危险。增加XX:DisableExplicitGC参数则可以禁用。去掉System。gc:禁用掉后会带来另一个内存泄漏的问题,为DirectByteBuffer分配空间过程中会显式调用System。gc,希望通过FullGC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的NativeMemory,如Netty等。策略
无论是保留还是去掉都会有一定的风险点,不过目前互联网中的RPC通信会大量使用NIO,所以建议保留。此外JVM还提供了XX:ExplicitGCInvokesConcurrent和XX:ExplicitGCInvokesConcurrentAndUnloadsClasses参数来将System。gc的触发类型从Foreground改为Background,同时Background也会做ReferenceProcessing,这样的话就能大幅降低了STW开销,同时也不会发生NIODirectMemoryOOM。
场景三:MetaSpace区OOM现象
JVM在启动后或者某个时间点开始,MetaSpace的已使用大小在持续增长,同时每次GC也无法释放,调大MetaSpace空间也无法彻底解决。原因
在讨论为什么会OOM之前,我们先来看一下这个区里面会存什么数据,Java7之前字符串常量池被放到了Perm区,所有被intern的String都会被存在这里,由于String。intern是不受控的,所以XX:MaxPermSize的值也不太好设置,经常会出现java。lang。OutOfMemoryError:PermGenspace异常,所以在Java7之后常量池等字面量(Literal)、类静态变量(ClassStatic)、符号引用(SymbolsReference)等几项被移到Heap中。而Java8之后PermGen也被移除,取而代之的是MetaSpace。
由场景一可知,为了避免弹性伸缩带来的额外GC消耗,我们会将XX:MetaSpaceSize和XX:MaxMetaSpaceSize两个值设置为固定的,但这样也会导致在空间不够的时候无法扩容,然后频繁地触发GC,最终OOM。所以关键原因是ClassLoader不停地在内存中load了新的Class,一般这种问题都发生在动态类加载等情况上。策略
可以dump快照之后通过JProfiler或MAT观察Classes的Histogram(直方图)即可,或者直接通过命令即可定位,jcmd打几次Histogram的图,看一下具体是哪个包下的Class增加较多就可以定位。如果无法从整体的角度定位,可以添加XX:TraceClassLoading和XX:TraceClassUnLoading参数观察详细的类加载和卸载信息。
场景四:过早晋升现象
这种场景主要发生在分代的收集器上面,专业的术语称为PrematurePromotion。90的对象朝生夕死,只有在Young区经历过几次GC的洗礼后才会晋升到Old区,每经历一次GC对象的GCAge就会增长1,最大通过XX:MaxTenuringThreshold来控制。过早晋升一般不会直接影响GC,总会伴随着浮动垃圾、大对象担保失败等问题,但这些问题不是立刻发生的,我们可以观察以下几种现象来判断是否发生了过早晋升:
分配速率接近于晋升速率,对象晋升年龄较小。
GC日志中出现Desiredsurvivorsize107347968bytes,newthreshold1(max6)等信息,说明此时经历过一次GC就会放到Old区。
FullGC比较频繁,且经历过一次GC之后Old区的变化比例非常大。
如Old区触发回收阈值是80,经历一次GC之后下降到了10,这说明Old区70的对象存活时间其实很短。过早晋升的危害:
YoungGC频繁,总的吞吐量下降
FullGC频繁,可能会有较大停顿原因
主要的原因有以下两点:
YoungEden区过小:过小的直接后果就是Eden被装满的时间变短,本应该回收的对象参与了GC并晋升,YoungGC采用的是复制算法,由基础篇我们知道copying耗时远大于mark,也就是YoungGC耗时本质上就是copy的时间(CMS扫描CardTable或G1扫描RememberSet出问题的情况另说),没来及回收的对象增大了回收的代价,所以YoungGC时间增加,同时又无法快速释放空间,YoungGC次数也跟着增加
分配速率过大:可以观察出问题前后Mutator的分配速率,如果有明显波动可以尝试观察网卡流量、存储类中间件慢查询日志等信息,看是否有大量数据被加载到内存中
策略如果是YoungEden区过小,可以在总的Heap内存不变的情况下适当增大Young区。一般情况下Old的大小应当为活跃对象的23倍左右,考虑到浮动垃圾问题最好在3倍左右,剩下的都可以分给Young区过早晋升优化来看,原配置为Young1。2GOld2。8G,通过观察CMSGC的情况找到存活对象大概为300400M,于是调整Old1。5G左右,剩下2。5G分给Young区。仅仅调了一个Young区大小参数(Xmn),整个JVM一分钟YoungGC从26次降低到了11次,单次时间也没有增加,总的GC时间从1100ms降低到了500ms,CMSGC次数也从40分钟左右一次降低到了7小时30分钟一次:如果是分配速率过大:偶发较大:通过内存分析工具找到问题代码,从业务逻辑上做一些优化一直较大:当前的Collector已经不满足Mutator的期望了,这种情况要么扩容Mutator的VM,要么调整GC收集器类型或加大空间小结
过早晋升问题一般不会特别明显,但日积月累之后可能会爆发一波收集器退化之类的问题,所以我们还是要提前避免掉的,可以看看自己系统里面是否有这些现象,如果比较匹配的话,可以尝试优化一下。一行代码优化的ROI还是很高的。如果在观察Old区前后比例变化的过程中,发现可以回收的比例非常小,如从80只回收到了60,说明我们大部分对象都是存活的,Old区的空间可以适当调大些。
场景五:CMSOldGC频繁现象
Old区频繁的做CMSGC,但是每次耗时不是特别长,整体最大STW也在可接受范围内,但由于GC太频繁导致吞吐下降比较多。原因
这种情况比较常见,基本都是一次YoungGC完成后,负责处理CMSGC的一个后台线程concurrentMarkSweepThread会不断地轮询,使用shouldConcurrentCollect()方法做一次检测,判断是否达到了回收条件。如果达到条件,使用collectinbackground()启动一次Background模式GC。轮询的判断是使用sleepBeforeNextCycle()方法,间隔周期为XX:CMSWaitDuration决定,默认为2s。策略
处理这种常规内存泄漏问题基本是一个思路,主要步骤如下:
DumpDiff和LeakSuspects比较直观,这里说下其它几个关键点:内存Dump:使用jmap、arthas等dump堆进行快照时记得摘掉流量,同时分别在CMSGC的发生前后分别dump一次分析TopComponent:要记得按照对象、类、类加载器、包等多个维度观察Histogram,同时使用outgoing和incoming分析关联的对象,其次SoftReference和WeakReference、Finalizer等也要看一下分析Unreachable:重点看一下这个,关注下Shallow和Retained的大小。如下图所示的一次GC优化,就根据UnreachableObjects发现了Hystrix的滑动窗口问题。
场景六:单次CMSOldGC耗时长现象
CMSGC单次STW最大超过1000ms,不会频繁发生,如下图所示最长达到了8000ms。某些场景下会引起雪崩效应,这种场景非常危险,我们应该尽量避免出现。
原因
CMS在回收的过程中,STW的阶段主要是InitMark和FinalRemark这两个阶段,也是导致CMSOldGC最多的原因,另外有些情况就是在STW前等待Mutator的线程到达SafePoint也会导致时间过长,但这种情况较少。策略
知道了两个STW过程执行流程,我们分析解决就比较简单了,由于大部分问题都出在FinalRemark过程,这里我们也拿这个场景来举例,主要步骤:
【方向】观察详细GC日志,找到出问题时FinalRemark日志,分析下Reference处理和元数据处理real耗时是否正常,详细信息需要通过XX:PrintReferenceGC参数开启。基本在日志里面就能定位到大概是哪个方向出了问题,耗时超过10的就需要关注。20190227T19:55:37。9200800:516952。915:〔GC(CMSFinalRemark)516952。915:〔ParNew516952。939:〔SoftReference,0refs,0。0003857secs〕516952。939:〔WeakReference,1362refs,0。0002415secs〕516952。940:〔FinalReference,146refs,0。0001233secs〕516952。940:〔PhantomReference,0refs,57refs,0。0002369secs〕516952。940:〔JNIWeakReference,0。0000662secs〕〔classunloading,0。1770490secs〕516953。329:〔scrubsymboltable,0。0442567secs〕516953。373:〔scrubstringtable,0。0036072secs〕〔1CMSremark:1638504K(2048000K)〕1667558K(4352000K),0。5269311secs〕〔Times:user1。20sys0。03,real0。53secs〕小结
正常情况进行的BackgroundCMSGC,出现问题基本都集中在Reference和Class等元数据处理上,在Reference类的问题处理方面,不管是FinalReference,还是SoftReference、WeakReference核心的手段就是找准时机dump快照,然后用内存分析工具来分析。Class处理方面目前除了关闭类卸载开关,没有太好的方法。在G1中同样有Reference的问题,可以观察日志中的RefProc,处理方法与CMS类似。
场景七:内存碎片收集器退化现象
并发的CMSGC算法,退化为Foreground单线程串行GC模式,STW时间超长,有时会长达十几秒。其中CMS收集器退化后单线程串行GC算法有两种:
带压缩动作的算法,称为MSC,上面我们介绍过,使用标记清理压缩,单线程全暂停的方式,对整个堆进行垃圾收集,也就是真正意义上的FullGC,暂停时间要长于普通CMS
不带压缩动作的算法,收集Old区,和普通的CMS算法比较相似,暂停时间相对MSC算法短一些原因
CMS发生收集器退化主要有以下几种情况:
晋升失败(PromotionFailed)
增量收集担保失败
显式GC
并发模式失败(ConcurrentModeFailure)策略
分析到具体原因后,我们就可以针对性解决了,具体思路还是从根因出发,具体解决策略:
内存碎片:通过配置XX:UseCMSCompactAtFullCollectiontrue来控制FullGC的过程中是否进行空间的整理(默认开启,注意是FullGC,不是普通CMSGC),以及XX:CMSFullGCsBeforeCompactionn来控制多少次FullGC后进行一次压缩
增量收集:降低触发CMSGC的阈值,即参数XX:CMSInitiatingOccupancyFraction的值,让CMSGC尽早执行,以保证有足够的连续空间,也减少Old区空间的使用大小,另外需要使用XX:UseCMSInitiatingOccupancyOnly来配合使用,不然JVM仅在第一次使用设定值,后续则自动调整
浮动垃圾:视情况控制每次晋升对象的大小,或者缩短每次CMSGC的时间,必要时可调节NewRatio的值。另外使用XX:CMSScavengeBeforeRemark在过程中提前触发一次YoungGC,防止后续晋升过多对象小结
正常情况下触发并发模式的CMSGC,停顿非常短,对业务影响很小,但CMSGC退化后,影响会非常大,建议发现一次后就彻底根治。只要能定位到内存碎片、浮动垃圾、增量收集相关等具体产生原因,还是比较好解决的,关于内存碎片这块,如果XX:CMSFullGCsBeforeCompaction的值不好选取的话,可以使用XX:PrintFLSStatistics来观察内存碎片率情况,然后再设置具体的值。最后就是在编码的时候也要避免需要连续地址空间的大对象的产生,如过长的字符串,用于存放附件、序列化或反序列化的byte数组等,还有就是过早晋升问题尽量在爆发问题前就避免掉。
场景八:堆外内存OOM现象
内存使用率不断上升,甚至开始使用SWAP内存,同时可能出现GC时间飙升,线程被Block等现象,通过top命令发现Java进程的RES甚至超过了Xmx的大小。出现这些现象时,基本可确定是出现堆外内存泄漏。原因
JVM的堆外内存泄漏,主要有两种的原因:
通过UnSafeallocateMemory,ByteBufferallocateDirect主动申请了堆外内存而没有释放,常见于NIO、Netty等相关组件
代码中有通过JNI调用NativeCode申请的内存没有释放策略
原因一:主动申请未释放
原因二:通过JNI调用的NativeCode申请的内存未释放
场景九:JNI引发的GC问题现象
在GC日志中,出现GCCause为GCLockerInitiatedGC。20200923T16:49:09。7270800:504426。742:〔GC(GCLockerInitiatedGC)504426。742:〔ParNew(promotionfailed):209716K6042K(1887488K),0。0843330secs〕1449487K1347626K(3984640K),0。0848963secs〕〔Times:user0。19sys0。00,real0。09secs〕20200923T16:49:09。8120800:504426。827:〔FullGC(GCLockerInitiatedGC)504426。827:〔CMS:1341583K419699K(2097152K),1。8482275secs〕1347626K419699K(3984640K),〔Metaspace:297780K297780K(1329152K)〕,1。8490564secs〕〔Times:user1。62sys0。20,real1。85secs〕原因
JNI(JavaNativeInterface)意为Java本地调用,它允许Java代码和其他语言写的Native代码进行交互。JNI如果需要获取JVM中的String或者数组,有两种方式:
拷贝传递
共享引用(指针),性能更高
由于Native代码直接使用了JVM堆区的指针,如果这时发生GC,就会导致数据错误。因此,在发生此类JNI调用时,禁止GC的发生,同时阻止其他线程进入JNI临界区,直到最后一个线程退出临界区时触发一次GC。
策略添加XXPrintJNIGCStalls参数,可以打印出发生JNI调用时的线程,进一步分析,找到引发问题的JNI调用JNI调用需要谨慎,不一定可以提升性能,反而可能造成GC问题升级JDK版本到14,避免JDK8048556导致的重复GC
美妆产品都有哪些?直男认为的化妆涂口红小仙女跟我来学正经的护肤美妆步骤一护肤顺序洁面洗面奶补水能量水润肤水眼霜棒状或罐妆,涂眼袋附近肌底液精华乳霜防晒,按步骤进行操作二化妆顺序防晒护肤的最后一步与上
如果一个女生内心极度自卑,体毛还多,学历低下,皮肤不好矮丑矬,甚至还有抑郁躁郁的倾向,那她该怎么办?你好,抱抱你。这些其实都不是问题。体毛多,攒钱去做激光脱毛。皮肤不好,从饮食结构开始改变,开始注意护理。如果资金不充裕的话很多国货也很棒。矮,穿衣服的时候注意比例,艾玛罗伯茨你知道
一般家庭,第一胎是儿子,还会考虑生二胎吗?我一胎就是男孩,二胎政策开放的时候,本没有打算生二胎,但是很突然,他就来了。当时知道的时候也很纠结,因为,政策已经放开,工作方面不用考虑,公司不能因为你生二胎辞退你,但是晋升肯定是
能否说一个零食的名字证明你已经老了?我是八零后,西北农村孩子,小时候零食还真不多,能记住的基本都是八十年代末九十年代初的东西了。(我只是想回忆童年,拒绝承认我老了呲牙)1,泡泡糖,条装的,咬嘴里除了甜味儿还有一股面粉
现如今的社会,当一个人开始用现金来消费时,意味着什么?本来就应该是用现金消费的受贿的钱手机没电了吧拉闸限电,手机没电!首先应该明确的是,人民币是法定货币,任何时候任何商家都不能够拒绝消费者使用人民币,也不能拒收人民币,也就是现金咯。不
为何大陆人到香港旅游一眼被看出不是本地人?大陆人到香港旅游,即使不开口,也很容易一眼看出来不是本地人,其实并没有什么特别的费解之处,很多细节都是自己透出来的。比如以下几点一,游客气质差异。大陆人到了香港,基本上不是旅游就是
我儿子通信工程硕士毕业。招聘去运营商好还是银行好?去运营商部门,有益于专业拓展,年轻人特别是技术人员从基层做起增加阅历,发展空间大一些。谢谢邀请,作为一个运营商从业人员,同时身边也了解不少银行从业人员(一方面因为运营商和银行业是互
成龙的女儿是个怎样的人?吴卓林并不像外界说的那样疯狂极端,也不是想从成龙身上得到些什么,如果多花一点时间对她有些了解,才发现她只是一个缺爱的女儿。说到吴卓林就不得不先说她母亲吴绮莉和成龙的故事。虽然成龙在
为什么很多事业单位节日加班没有加班费了?很多事业单位加班没有加班费,原因如下首先,加班不同于节假日值班,比如你下班以后,因为工作有一点没做完,自己选择加班,或者是周末因为临时工作,需要加班工作,这些情况下加班,都不会有加
事业单位临时工有年终奖吗?单位办公室有个文员,是经报人社编制和财政部门审核同意,面向社会公开招聘的合同制聘用人员。其工资待遇由财政全额负担,每月扣除五险一金后到手大概在2700左右,节假日加班有加班费。平常
我家女儿慢性胃炎,该如何调理?慢性胃炎注意事项1控制饮食,吃较为容易消化的实物2控制情绪,情绪不佳也可影响胃炎3可选择中药进行对症治疗。以故国医大师李振华温中汤对于慢性胃炎效果不错。首先我建议父母也检查一下胃镜
三生有幸薛邃老先生的画,通古现代属于未来,我们今天能与邃遇,真是三生有幸。谐音梗大行其道的今天,也玩到了薛邃老先生这厢。晚辈为老人办画展,起的名字,不是邃心所欲,就是邃遇而安。这种小机灵小
陆游最温柔的30句诗词,硬汉也有柔情众所周知,陆游是一个爱国者,是一个硬汉。但是,硬汉也有柔情。陆游爱唐婉,伴着思念,他想了一生。陆游也还喜欢猫,喜欢明月,喜欢美丽的生活。30句陆游最温柔的诗词,硬汉也有柔情,你信,
怎样加钉钉群?怎样加钉钉群?昨天学校要求老师们参加线上培训,给了个二维码让加群,但是没有说是什么二维码。我在微信,QQ上都试了一遍,结果都不行。就在钉钉上扫码,扫码成功,但是显示群人数已经满了,
在日常生活中,到底该如何保护我们的听力,避免伤害呢?1。保护听力远离噪音源日常生活中,噪音几乎无处不在家庭中,常见的噪音可能来自各类家用电器,有人喜欢把音响电视机的声音开到很大,以获得更好的临场效果,长此以往就会对听力造成损伤。居住
敏感肌救星积雪草,未来大有可为要说近年来比较火的化妆品成分,积雪草便是其中一种。积雪草,又名马蹄草,是一种古老的药物,具有清热祛湿解毒消肿的功效,至今已有数千年历史,在我国神农本草经本草纲目中均有记载。而随着科
(五)你过得轻松吗现在人们逐渐关注个人内心感受,一方面源于对外部世界海量信息的获取,这些信息都触发人的思考。另一方面在于人与生俱来对自身认识的渴望,在这个物质丰富的时代变得更加强烈。心路是最难走的,
远程办公访问轻松搞定,蒲公英X6使用体验这段时间好多企业都开始AB角办公,一半居家一半在公司,这样即保证了工作不断档,又保证了大家的人身安全,此时就需要用到异地协同办公方案了。如果是大公司大企业肯定有成熟方案,小微企业的
老婆孕期便秘学会做这三种食物,轻松应对!保准畅通!便秘是很多人都经历过的事情,特别是一些怀孕的女性,几乎都有过便秘的情况。随着孕期的不断延长,便秘的症状也会越来越严重,在孕晚期的时候经常都是连续几天不大便,甚至还会引发腹痛和腹胀的
曝RedmiK60系列即将发布,小米11系列更新售后12月19日比亚发布了首款售价在3000以下的骁龙8Gen2手机,这个价格甚至比摩托X40还要低400,很多人都说现在压力来到了卢伟冰这边,但要说手机极致性价比,那还得看Redmi
芯片引脚断了,没关系,剥开去盖后飞线还能用01芯片去盖飞线我们拿到一个芯片,其实芯片核心部分并没有芯片这么大,为了方便引出众多的引脚,所以才在外面又多封装了一层,常见的有金属封装塑料封装陶瓷封装等,一般消费级别的大多用塑料
插电式混动纯电燃油车型,哪个更合适大冬天开?其实,以往我答过很多关于插混,纯电,燃油怎么选的题目。一般都是建议各位落在使用场景,结合个人喜好,又或者对比三者优缺点。但对于混动,尤其是插混。不得不说,他的的确确可以作为当下冬日