掌握JVM一篇就够

13 篇文章 0 订阅

1. 追本溯源——堆和栈

堆通常是一个可以被看做一棵树的数组对象,栈是一种只能在一端进行插入和删除操作的先进后出线性表,JVM的本质是堆和栈

第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

2. JVM运行机制


此图看出jvm内存结构

JVM内存结构主要包括两个子系统和两个组件。两个子系统分别是Classloader子系统和Executionengine(执行引擎)子系统;两个组件分别是Runtimedataarea(运行时数据区域)组件和Nativeinterface(本地接口)组件。

Classloader子系统的作用:根据给定的全限定名类名(如java.lang.Object)来装载class文件的内容到Runtimedataarea中的methodarea(方法区域)。Java程序员可以extendsjava.lang.ClassLoader类来写自己的Classloader。

Executionengine子系统的作用:执行classes中的指令。任何JVMspecification实现(JDK)的核心都是Executionengine,不同的JDK例如Sun的JDK和IBM的JDK好坏主要就取决于他们各自实现的Executionengine的好坏。

Nativeinterface组件:与nativelibraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的nativeheapOutOfMemory。

RuntimeDataArea组件:这就是我们常说的JVM的内存了

JVM的生命周期
一、首先分析两个概念
   JVM实例和JVM执行引擎实例
  (1)JVM实例对应了一个独立运行的java程序,它是进程级别。
  (2)JVM执行引擎实例则对应了属于用户运行程序的线程,它是线程级别的。

二、JVM的生命周期
  (1)JVM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
  (2)JVM实例的运行 main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
  (3)JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。

3. 类的生命

一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况

双亲委派模型是一种组织类加载器之间关系的一种规范,他的工作原理是:如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载.

4. 认识JVM内存模型


对于JVM内存,程序员只需要关注

  • 对象去哪儿——堆内存(包括新生代和老年代,新生代又分为Enden区和两个Survivor区),堆内存用于存放对象,合理分区主要是为了提高垃圾回收效率,新创建的对象会在Enden区,经历Minor GC后会到Survivor区,经历一般15次GC还存活的话会进入老年代。
  • 函数如何调用——栈内存用于运行线程,它们包含了方法里的临时数据、堆里其它对象引用的特定数据。
  • 类去哪儿——方法区用来存储类型信息(运行时常量和静态变量)和方法代码和构造函数代码,通常也叫永久代,JDK8用元空间代替永久代

JVM资源一般分为两种CPU和内存,CPU代表着线程栈,内存代表着堆,而往往生产环境JVM问题一般都是这两种资源不足导致的,当线程栈使用不当的时候,通常会CPU爆满,当堆内存使用不当的时候,通常会出现内存溢出。

5. 内存管理清洁工——垃圾回收

Java程序语言中的一个最大优点是自动垃圾回收,Java垃圾回收会找出没用的对象,把它从内存中移除并释放出内存给以后创建的对象使用。下面图片生动对比手动垃圾回收和自动垃圾回收的区别。垃圾回收重点关注碎片和性能。

初识垃圾回收算法

5.1 按回收策略

(1).引用计数算法:
 


给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。引用计数算法实现简单,效率很高,微软的COM技术、ActionScript、Python等都使用了引用计数算法进行内存管理,但是引用计数算法对于对象之间相互循环引用问题难以解决,因此java并没有使用引用计数算法。

(2).根搜索算法:


通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。

在Java语言里,可作为GC Roots对象的包括如下几种:
a.System Class,像rt.jar里面的java.util.*
b.Thread,开始状态的线程
c.虚拟机栈(栈桢中的本地变量表)中的引用的对象
d.方法区中的类静态属性引用的对象
e.方法区中的常量引用的对象
f.本地方法栈中JNI的引用的对象

(3).标记-清除算法:
 


最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。

标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

(4).复制算法:


将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

复制算法的缺点显而易见,可使用的内存降为原来一半。

(5).标记-清除-整理算法:


标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

5.2 按分区回收

(1).分代算法:


根据对象的存活周期的不同将内存划分为几块。一般把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-整理”算法进行回收。

5.3 按系统线程

串行/并行回收方式:在GC过程中,单线程/多线程收集器采用Stop-the-World机制,做事专一吞吐量高;
并发回收:工作线程和垃圾回收线程并发执行,一心二用,吞吐量较低,不过保证在GC的时候,其它用户线程可以工作;

5.4 常见垃圾收集器对比

6. JVM常见参数

最后汇总一下JVM常见配置

6.1 类加载设置

  • -XX:+TraceClassLoading:类加载日志
  • -XX:+TraceClassUnloading:类卸载日志

6.2 堆设置

  • -Xms:初始堆大小
  • -Xmx:最大堆大小
  • -XX:NewSize=n:设置年轻代大小
  • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
  • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
  • -XX:MaxPermSize=n:设置持久代大小

6.3 垃圾回收器设置

  • -XX:+UseSerialGC:设置串行收集器
  • -XX:+UseParallelGC:设置并行收集器
  • -XX:+UseParalledlOldGC:设置并行年老代收集
  • -XX:+UseConcMarkSweepGC:设置并发收集器

6.4 垃圾回收统计信息

  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintGCTimeStamps
  • -Xloggc:filename

6.5 并行垃圾回收器参数设置

  • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
  • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
  • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

6.6 并发垃圾回收器设置

  • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况
  • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

7. 常见问题

7.1 Young GC、Mixed GC和Full GC的区别?

答:Young GC的CSet中只包括年轻代的分区,Mixed GC的CSet中除了包括年轻代分区,还包括老年代分区;Full GC会暂停整个引用,同时对年轻代和老年代进行收集和压缩。

7.2 ParallelGCThreads和ConcGCThreads的区别?

答:ParallelGCThreads指得是在STW阶段,并行执行垃圾收集动作的线程数,ParallelGCThreads的值一般等于逻辑CPU核数,如果CPU核数大于8,则设置为5/8 * cpus,在SPARC等大型机上这个系数是5/16。;ConcGCThreads指的是在并发标记阶段,并发执行标记的线程数,一般设置为ParallelGCThreads的四分之一。

7.3 write barrier在GC中的作用?

如何理解G1 GC中write barrier的作用? 写屏障是一种内存管理机制,用在这样的场景——当代码尝试修改一个对象的引用时,在前面放上写屏障就意味着将这个对象放在了写屏障后面。write barrier在GC中的作用有点复杂,我们这里以trace GC算法为例讲下:trace GC有些算法是并发的,例如CMS和G1,即用户线程和垃圾收集线程可以同时运行,即mutator一边跑,collector一边收集。这里有一个限制是:黑色的对象不应该指向任何白色的对象。如果mutator视图让一个黑色的对象指向一个白色的对象,这个限制就会被打破,然后GC就会失败。针对这个问题有两种解决思路:(1)通过添加read barriers阻止mutator看到白色的对象;(2)通过write barrier阻止mutator修改一个黑色的对象,让它指向一个白色的对象。write barrier的解决方法就是讲黑色的对象放到写write barrier后面。如果真得发生了white-on-black这种写需求,一般也有多种修正方法:增量得将白色的对象变灰,将黑色的对象重新置灰等等。我理解,增量的变灰就是CMS和G1里并发标记的过程,将黑色的对象重新变灰就是利用卡表或SATB的缓冲区将黑色的对象重新置灰的过程,当然会在重新标记中将所有灰色的对象处理掉。

8. 关于G1回收器

8.1 G1的调优

G1的调优目标主要是在避免FULL GC和疏散失败的前提下,尽量实现较短的停顿时间和较高的吞吐量。关于G1 GC的调优,需要记住以下几点:

  1. 不要自己显式设置年轻代的大小(用Xmn或-XX:NewRatio参数),如果显式设置年轻代的大小,会导致目标时间这个参数失效。

  2. 由于G1收集器自身已经有一套预测和调整机制了,因此我们首先的选择是相信它,即调整-XX:MaxGCPauseMillis=N参数,这也符合G1的目的——让GC调优尽量简单,这里有个取舍:如果减小这个参数的值,就意味着会调小年轻代的大小,也会导致年轻代GC发生得更频繁,同时,还会导致混合收集周期中回收的老年代分区减少,从而增加FULL GC的风险。这个时间设置得越短,应用的吞吐量也会受到影响。

  3. 针对混合垃圾收集的调优。如果调整这期望的最大暂停时间这个参数还是无法解决问题,即在日志中仍然可以看到FULL GC的现象,那么就需要自己手动做一些调整,可以做的调整包括:

  • 调整G1垃圾收集的后台线程数,通过设置-XX:ConcGCThreads=n这个参数,可以增加后台标记线程的数量,帮G1赢得这场你追我赶的游戏;

  • 调整G1垃圾收集器并发周期的频率,如果让G1更早得启动垃圾收集,也可以帮助G1赢得这场比赛,那么可以通过设置-XX:InitiatingHeapOccupancyPercent这个参数来实现这个目标,如果将这个参数调小,G1就会更早得触发并发垃圾收集周期。这个值需要谨慎设置:如果这个参数设置得太高,会导致FULL GC出现得频繁;如果这个值设置得过小,又会导致G1频繁得进行并发收集,白白浪费CPU资源。通过GC日志可以通过一个点来判断GC是否正常——在一轮并发周期结束后,需要确保堆剩下的空间小于InitiatingHeapOccupancyPercent的值。

  • 调整G1垃圾收集器的混合收集的工作量,即在一次混合垃圾收集中尽量多处理一些分区,可以从另外一方面提高混合垃圾收集的频率。在一次混合收集中可以回收多少分区,取决于三个因素:(1)有多少个分区被认定为垃圾分区,-XX:G1MixedGCLiveThresholdPercent=n这个参数表示如果一个分区中的存活对象比例超过n,就不会被挑选为垃圾分区,因此可以通过这个参数控制每次混合收集的分区个数,这个参数的值越大,某个分区越容易被当做是垃圾分区;(2)G1在一个并发周期中,最多经历几次混合收集周期,这个可以通过-XX:G1MixedGCCountTarget=n设置,默认是8,如果减小这个值,可以增加每次混合收集收集的分区数,但是可能会导致停顿时间过长;(3)期望的GC停顿的最大值,由MaxGCPauseMillis参数确定,默认值是200ms,在混合收集周期内的停顿时间是向上规整的,如果实际运行时间比这个参数小,那么G1就能收集更多的分区。

8.2 G1的最佳实践

8.2.1 关键参数项
  • -XX:+UseG1GC,告诉JVM使用G1垃圾收集器

  • -XX:MaxGCPauseMillis=200,设置GC暂停时间的目标最大值,这是个柔性的目标,JVM会尽力达到这个目标

  • -XX:INitiatingHeapOccupancyPercent=45,如果整个堆的使用率超过这个值,G1会触发一次并发周期。记住这里针对的是整个堆空间的比例,而不是某个分代的比例。

8.2.2 最佳实践

不要设置年轻代的大小

通过-Xmn显式设置年轻代的大小,会干扰G1收集器的默认行为:

  • G1不再以设定的暂停时间为目标,换句话说,如果设置了年轻代的大小,就无法实现自适应的调整来达到指定的暂停时间这个目标

  • G1不能按需扩大或缩小年轻代的大小

8.2.3 响应时间度量

不要根据平均响应时间(ART)来设置-XX:MaxGCPauseMillis=n这个参数,应该设置希望90%的GC都可以达到的暂停时间。这意味着90%的用户请求不会超过这个响应时间,记住,这个值是一个目标,但是G1并不保证100%的GC暂停时间都可以达到这个目标

8.3 G1 GC的参数选项

格式:参数名含义默认值

  • -XX:+UseG1GC使用G1收集器JDK1.8中还需要显式指定
  • -XX:MaxGCPauseMillis=n设置一个期望的最大GC暂停时间,这是一个柔性的目标,JVM会尽力去达到这个目标200
  • -XX:InitiatingHeapOccupancyPercent=n当整个堆的空间使用百分比超过这个值时,就会触发一次并发收集周期,记住是整个堆45
  • -XX:NewRatio=n年轻代和老年代的比例2
  • -XX:SurvivorRatio=nEden空间和Survivor空间的比例8
  • -XX:MaxTenuringThreshold=n对象在年轻代中经历的最多的年轻代收集,或者说最大的岁数G1中是15
  • -XX:ParallelGCThreads=n设置垃圾收集器的并行阶段的垃圾收集线程数不同的平台有不同的值
  • -XX:ConcGCThreads=n设置垃圾收集器并发执行GC的线程数n一般是ParallelGCThreads的四分之一
  • -XX:G1ReservePercent=n设置作为空闲空间的预留内存百分比,以降低目标空间溢出(疏散失败)的风险。默认值是 10%。增加或减少这个值,请确保对总的 Java 堆调整相同的量10
  • -XX:G1HeapRegionSize=n分区的大小堆内存大小的1/2000,单位是MB,值是2的幂,范围是1MB到32MB之间
  • -XX:G1HeapWastePercent=n设置您愿意浪费的堆百分比。如果可回收百分比小于堆废物百分比,JavaHotSpotVM不会启动混合垃圾回收周期(注意,这个参数可以用于调整混合收集的频率)。JDK1.8是5
  • -XX:G1MixedGCCountTarget=8设置并发周期后需要执行多少次混合收集,如果混合收集中STW的时间过长,可以考虑增大这个参数。(注意:这个可以用来调整每次混合收集中回收掉老年代分区的多少,即调节混合收集的停顿时间)8
  • -XX:G1MixedGCLiveThresholdPercent=n一个分区是否会被放入mix GC的CSet的阈值。对于一个分区来说,它的存活对象率如果超过这个比例,则改分区不会被列入mixed gc的CSet中JDK1.6和1.7是65,JDK1.8是85

8.4 常见问题

  1. Young GC、Mixed GC和Full GC的区别? 答:Young GC的CSet中只包括年轻代的分区,Mixed GC的CSet中除了包括年轻代分区,还包括老年代分区;Full GC会暂停整个引用,同时对年轻代和老年代进行收集和压缩。

  2. ParallelGCThreads和ConcGCThreads的区别? 答:ParallelGCThreads指得是在STW阶段,并行执行垃圾收集动作的线程数,ParallelGCThreads的值一般等于逻辑CPU核数,如果CPU核数大于8,则设置为5/8 * cpus,在SPARC等大型机上这个系数是5/16。;ConcGCThreads指的是在并发标记阶段,并发执行标记的线程数,一般设置为ParallelGCThreads的四分之一。

  3. write barrier在GC中的作用?如何理解G1 GC中write barrier的作用? 写屏障是一种内存管理机制,用在这样的场景——当代码尝试修改一个对象的引用时,在前面放上写屏障就意味着将这个对象放在了写屏障后面。write barrier在GC中的作用有点复杂,我们这里以trace GC算法为例讲下:trace GC有些算法是并发的,例如CMS和G1,即用户线程和垃圾收集线程可以同时运行,即mutator一边跑,collector一边收集。这里有一个限制是:黑色的对象不应该指向任何白色的对象。如果mutator视图让一个黑色的对象指向一个白色的对象,这个限制就会被打破,然后GC就会失败。针对这个问题有两种解决思路:(1)通过添加read barriers阻止mutator看到白色的对象;(2)通过write barrier阻止mutator修改一个黑色的对象,让它指向一个白色的对象。write barrier的解决方法就是讲黑色的对象放到写write barrier后面。如果真得发生了white-on-black这种写需求,一般也有多种修正方法:增量得将白色的对象变灰,将黑色的对象重新置灰等等。我理解,增量的变灰就是CMS和G1里并发标记的过程,将黑色的对象重新变灰就是利用卡表或SATB的缓冲区将黑色的对象重新置灰的过程,当然会在重新标记中将所有灰色的对象处理掉。

8.5 G1回收器的适用场景

  • 面向服务端应用,针对具有大内存、多处理器的机器,需要较低延迟的场景

  • 最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案;如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;(G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长)。

  • G1 作为 CMS 的代替者出现,解决了 CMS 中的各种疑难问题,包括暂停时间的可预测性,并终结了堆内存的碎片化。对单业务延迟非常敏感的系统来说,如果 CPU 资源不受限制,那么 G1 可以说是 HotSpot 中最好的选择,特别是在最新版本的 JVM 中。当然这种降低延迟的优化也不是没有代价的:由于额外的写屏障和守护线程,G1 的开销会更大。如果系统属于吞吐量优先型的,又或者 CPU 持续占用 100%,而又不在乎单次 GC 的暂停时间,那么 CMS 是更好的选择。在下面的情况时,使用G1可能比CMS好:HotSpot 垃圾收集器里,除了G1以外,其他的垃圾收集器使用内置的JVM线程执行GC的多线程操作,而G1 GC可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程。

    • 超过50%的Java堆被活动数据占用;
    • 对象分配频率或年代提升频率变化很大;
    • GC停顿时间过长(长于0.5至1秒)

8.6 G1和CMS的区别

  • G1从整体上来看是 标记-整理 算法,但从局部(两个Region之间)是复制算法。而CMS是 标记-清除算法 所以说,G1不会产生内存碎片,而CMS会产生内存碎片

  • CMS使用了 写后屏障来维护卡表,而G1不仅使用了写后屏障来维护卡表,还是用了 写前屏障来跟踪并发时的指针变化情况(为了实现原始快照)。

  • CMS对Java堆内存使用的是传统的 年轻代和老年代划分方法,而G1使用的全新的划分方法。

  • CMS收集器只收集老年代,可以配合年轻代的Serial和ParNew收集器一起使用。G1收集器收集范围是老年代和年轻代。不需要结合其他收集器使用

  • CMS使用 增量更新解决并发标记下出现的错误标记问题,而G1使用原始快照解决

  • G1 的预测模型也会有失效的时候,它并不是总如我们期望的那样运行,尤其是给它定下一个苛刻的目标之后。另外,如果应用的内存非常吃紧,对内存进行部分回收根本不够,始终要进行整个 Heap 的回收,那么 G1 要做的工作量就一点也不会比其他垃圾回收器少,而且因为本身算法复杂了,还可能比其他回收器要差。 

8.7 关于ZGC收集器

Z 垃圾回收器 (ZGC) 是一种可扩展的低延迟垃圾回收器。ZGC同时执行所有昂贵的工作,而不会停止应用程序线程的执行。

ZGC提供几毫秒的最长暂停时间,但代价是一些 吞吐量。它适用于需要低延迟的应用程序。暂停时间为 与正在使用的堆大小无关。ZGC支持从8MB到16TB的堆大小。 要启用此功能,请使用-XX:+UseZGC选项。

最新的 ZGC 垃圾回收器,就有 3 个令人振奋的 Flag:

  1. 停顿时间不会超过 10ms;
  2. 停顿时间不会随着堆的增大而增大(不管多大的堆都能保持在 10ms 以下);
  3. 可支持几百 M,甚至几 T 的堆大小(最大支持 4T)。

在 ZGC 中,连逻辑上的年轻代和老年代也去掉了,只分为一块块的 page,每次进行 GC 时,都会对 page 进行压缩操作,所以没有碎片问题。ZGC 还能感知 NUMA 架构,提高内存的访问速度。与传统的收集算法相比,ZGC 直接在对象的引用指针上做文章,用来标识对象的状态,所以它只能用在 64 位的机器上

现在在线上使用 ZGC 的还非常少。即使是用,也只能在 Linux 平台上使用。等待它的普及,还需要一段时间。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风情客家__

原创不易,觉得好的话给个打赏哈

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值