jvm基础理论。

前言

我们常说的JVM调优,到底指的是什么?你应该牢记一个名词stop-the-world,无论您选择哪种 GC 算法,都会发生stop-the-worldstop-the-world意味着 JVM 因为要执行GC而停止了应用程序的执行。当stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态,直到GC任务完成。我们常说的JVM调优大多数指的是减少stop-the-world时间

jvm基础理论。

建议不要使用System.gc()

在JAVA中,不能显示的分配和删除内存,大多数程序员会将相关对象设置为null,或者使用System.gc()方法显示删除内存。设置为null没什么坏处,但是调用System.gc()却是值得考虑的。

•在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。•JVM实现者可以通过system.gc()调用来决定JVM的GC行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。•然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用(无法保证马上触发GC)。

年轻代和老年代

gc()进行回收的准则是什么?也就是说什么样的对象可以被回收?

没有被任何可达变量指向的对象。这里的可达是意思就是能够找到的(没有任何可达变量指向你,你还有活下去的理由吗?你就算活下去谁能找得到你呢?)

HotSpot VM 认为大多数对象拥有以下特性

•大多数对象很快就会变得无法访问。•只有极少数创建时间较长的对象存在引用

所以在HotSpot VM 中,它在物理上分为两个 -年轻代(young generation)和老年代(old generation)。

年轻代(Young generation):大部分新创建的对象都被分配在这里,由于大多数对象很快变得不可访问,因此许多对象在年轻代中创建,然后消失。对象从该区域消失的过程,我们称为minor GC

老年代(Old generation): 没有不可访问并且从年轻代幸存下来的对象被复制到这里,通常所占用的空间比年轻代大。因为占用较大的空间,所以发生gc的频率远比年轻代低。对象从老年代消失的过程,我们称为full GC

jvm基础理论。

上图中的永久代( permanent generation )也被称为方法区(method area)。用来保存类常量以及字符串常量。因此,这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC。并且发生在这个区域上的GC事件也会被算为major GC

Card Table

如果老年代的对象需要引用年轻代的对象怎么办? 存在一个”card table“,他是一个512 byte大小的块。所有老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只需要查询card table来决定是否可以被收集,而不用查询整个老年代。这个card table由一个write barrier来管理。write barrier给GC带来了很大的性能提升,虽然由此可能带来一些开销,但GC的整体时间被显著的减少。jvm基础理论。

年轻代的构成

对象第一次被创建的地方。年轻代分为3个空间。

•一个伊甸园空间(Eden )•两个幸存者空间(Survivor )

总共有3个空间,其中两个是幸存者空间。各个空间的执行过程顺序如下:

1.大多数新创建的对象都位于 Eden 空间中。 2.在 Eden 空间中进行一次 GC 后,将幸存的对象移动到 Survivor 空间之一。 3.在 Eden 空间进行一次 GC 后,对象被堆积到 Survivor 空间中,其他幸存对象已经存在。 4.一旦幸存者空间已满,幸存的对象将被移动到另一个幸存者空间。然后,已满的 Survivor 空间将变为完全没有数据的状态。 5.在这些重复多次的步骤中幸存下来的对象被移动到老年代。

正如您通过检查这些步骤所看到的,Survivor 空间之一必须保持为空。如果两个 Survivor 空间中都存在数据,或者两个空间的使用量均为 0,则将其视为系统出现问题的标志。

数据通过Minor GC堆积到老年代的过程如下图所示:jvm基础理论。

需要注意的是HotSpot虚拟机使用了两种技术来加快内存分配。他们分别是是”bump-the-pointer“和“TLABs(Thread-Local Allocation Buffers)”。

Bump-the-pointer技术跟踪在伊甸园空间创建的最后一个对象。这个对象会被放在伊甸园空间的顶部。如果之后再需要创建对象,只需要检查伊甸园空间是否有足够的剩余空间。如果有足够的空间,对象就会被创建在伊甸园空间,并且被放置在顶部。这样以来,每次创建新的对象时,只需要检查最后被创建的对象。这将极大地加快内存分配速度。但是,如果我们在多线程的情况下,事情将截然不同。如果想要以线程安全的方式以多线程在伊甸园空间存储对象,不可避免的需要加锁,而这将极大地的影响性能。TLABs 是HotSpot虚拟机针对这一问题的解决方案。该方案为每一个线程在伊甸园空间分配一块独享的空间,这样每个线程只访问他们自己的TLAB空间,再与bump-the-pointer技术结合可以在不加锁的情况下分配内存。

总结:在对象刚刚被创建之后,是保存在伊甸园空间的。那些长期存活的对象会经由幸存者空间转存在老年代空间。

老年代GC

老年代空间的GC事件基本上是在空间已满时发生,执行过程因 GC 类型而异,因此,了解不同的GC类型将更容易理解。

•Serial GC•Parallel GC•Parallel Old GC (Parallel Compacting GC)•Concurrent Mark & Sweep GC (or “CMS”)•Garbage First (G1) GC

1.Serial GC (-XX:+UseSerialGC) 年轻代中的 GC 使用我们在上一段中解释的类型。老年代的 GC 使用了一种叫做“ mark-sweep-compact ”的算法。

•该算法的第一步是标记老年代中的幸存对象。(标机)•然后,从头开始检查堆内存空间,并且只留下依然幸存的对象(清理)。•最后一步,从头开始,顺序地填满堆内存空间,并且将对内存空间分成两部分:一个保存着对象,另一个空着(压缩)。

Serial GC适用于内存小、CPU核数少的情况。

2.Parallel GC (-XX:+UseParallelGC)jvm基础理论。从上图中,可以看出serial GC和parallel GC的区别,serial GC只使用一个线程执行GC,而parallel GC使用多个线程,因此parallel GC更高效。这种GC在内存充足以及多核的情况下会很有用,因此我们也称之为”throughput GC“。

3.Parallel Old GC(-XX:+UseParallelOldGC) Parallel Old GC在JDK5之后出现。与parallel GC相比,唯一的区别在于针对老年代的GC算法。Parallel Old GC分为三步:标记-汇总-压缩(mark – summary – compaction)。汇总(summary)步骤与清理(sweep)的不同之处在于,其将依然幸存的对象分发到GC预先处理好的不同区域,算法相对清理来说略微复杂一点。

1.CMS GC (-XX:+UseConcMarkSweepGC)jvm基础理论。

CMS GC比上面解释的各种算法都要复杂很多。第一步初始化标记(initial mark) 比较简单。这一步骤只是查找那些距离类加载器最近的幸存对象。因此,停顿的时间非常短暂。在之后的并行标记( concurrent mark )步骤,所有被幸存对象引用的对象会被确认是否已经被追踪和校验。这一步的不同之处在于,在标记的过程中,其他的线程依然在执行。在重新标记(remark)步骤,会再次检查那些在并行标记步骤中增加或者删除的与幸存对象引用的对象。最后,在并行交换( concurrent sweep )步骤,转交垃圾回收过程处理。垃圾回收工作会在其他线程的执行过程中展开。一旦采取了这种GC类型,由GC导致的暂停时间会极其短暂。CMS GC也被称为低延迟GC。它经常被用在那些对于响应时间要求十分苛刻的应用之上。 当然,这种GC类型在拥有stop-the-world时间很短的优点的同时,也有如下缺点:

•它会比其他GC类型占用更多的内存和CPU•默认情况下不支持压缩步骤

在使用这个GC类型之前你需要慎重考虑。如果因为内存碎片过多而导致压缩任务不得不执行,那么stop-the-world的时间要比其他任何GC类型都长,你需要考虑压缩任务的发生频率以及执行时间。

5 G1 GC

jvm基础理论。

如果你想要理解G1,首先你要忘记你所学过的新生代和老年代的概念。正如你在上图所看到的,每个对象被分配到不同的格子,随后GC执行。当一个区域装满之后,对象被分配到另一个区域,并执行GC。这中间不再有从新生代移动到老年代的三个步骤,G1最大的好处是性能。

查看当前jdk默认使用的gc收集器

java -XX:+PrintCommandLineFlags -version
上一篇:HTML初学笔记——字节青训营


下一篇:HTML5 拖放(Drag 和 Drop)