jvm学习小结二----垃圾收集器与内存分配策略

1、堆和方法区需要回收

内存运行时区域有,程序计数器、虚拟机栈、本地方法栈、方法区、堆。其中程序计数器、虚拟机栈、本地方法栈随线程而生,随线程而灭,且栈帧的大小再编译时就已经确定了,因此这几个区域的分配和回收不需要过多考虑。而堆和方法区不一样,比如一个接口中多个实现类需要的内存不一样,一个方法的不同分支需要的内存可能不一样,因此这些内存的分配和回收都是动态的。

2、判断对象存活的算法有引用计数算法和可达性分析算法(常用)

引用计数算法:给对象添加一个引用计数器,有地方引用它就加一,引用失效就减一,为0说明不在使用。缺点是不能解决对象间相互循环引用的问题。

可达性分析算法:通过一些列称为“GC Roots”的对象为起点,向下搜索,搜索走过的路径称为引用链,当一个对象没有到达GC Roots的引用链时,则次对象不可以用。

3、可作为GC Roots的对象:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(native方法)引用的对象。

4、4种引用

  1. 强引用(Strong Reference):类似Object obj=new Object()这类的引用,只要引用存在就不会被GC收回。
  2. 软引用(Soft Reference):描述有用但非必须的对象。对于被关联的对象,在系统将要发生内存溢出异常之前,才会将这些对象回收。使用SoftReference实现软引用。
  3. 弱引用(Weak Reference):描述非必需对象。被关联的对象只能生存到下一次垃圾收集发生之前。使用WeakRefence类来实现。
  4. 虚引用(Phantom Reference):无法使用虚引用来获得对象实例。使用虚引用的唯一目的是在这个对象被收集器回收时收到一个系统通知。使用PhantomReference类实现。

系统的缓存功能适合使用其他引用。

5、可达性分析算法中的不可达对象并非必死,可以通过finalize()函数使它被引用,但是finalize方法只会被虚拟机调用一次。finalize()执行太久会被虚拟机终止。

6、在HotSpot中,方法区被当作堆中的永久代。收回时需要回收两部分:废弃常量和无用类。常量的回收和堆中对象的回收类似。判断一个类是否是“无用的类”需要满足下列三个条件:

  1. 该类所有实例都被回收
  2. 加载该类的ClassLoader被回收
  3. 对应的Class对象没有在任何地方被引用。

7、垃圾收集算法

  1. 标记-清除(Mark-Sweep)算法:先标记需要回收的对象,之后回收。不足:标记和清除效率不高;容易产生内存碎片。
  2. 复制算法(常用):将内存分为大小相等的两块,每次使用其中一块。当这一块使用完了,就将活着的对象复制到另一块中,而这一块内存全被回收。这样效率提高了,也不存在内存碎片的问题,但是代价是将内存缩小了一半。商业虚拟机在新生代中使用了该手机算法,但是略有点不同,比如将内存分为了较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。回收时,将Eden和Survivor中活着的对象复制到另一块Survivor中。因为新生代中98%的对象是“朝生暮死”的,因此,一般情况下,一个Survivor可以存放下活着的对象。极端情况下存放不了则进行分配担保,也就是借用下老年代的内存。
  3. 标记-整理(Mark-Compact)算法:复制收集算法在对象存活率较高时效率低,但浪费一定空间,因而老年代一般使用标记-整理算法。该算法先标记对象,回收时将存活对象往一端移动,清理其他对象。该算法不需要额外空间。
  4. 分代收集算法:java根据对象存活周期将堆分为新生代和老年代,因此对不同年代可取不同收集算法。新生代每次垃圾收集都会有大量对象死去,少量存活,适合使用复制算法。老年代存活率高、没有额外空间进行分配担保,适合标记-清理或者标记-整理算法。

8、垃圾收集算法都会造成GC停顿,停顿java所有执行线程。停顿时,所有线程需要执行到安全点(SafePoint)或者安全区域(Safe Region)才可停顿。

9、垃圾收集器

下图中Seril、ParNew、Parallel Scavenge用于新生代垃圾收集,CMS、Serial Old、Parallel Old用于老年代垃圾收集,G1同时用于新生代和老年代。收集器之间的连线表示相互之间可以搭配使用。

9.1、Serial收集器

单线程,进行垃圾收集时必须暂停其他所有的工作线程。client模式下的默认新生代收集器。在单cpu,没有线程交互的开销,效率更高。在桌面应用中,新生代的内存不大,停顿时间短,只要不太频繁进行垃圾收集,这点停顿还是可以接受的。

9.2、ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,共用了相当多的代码。是Server模式下的首选新生代收集器,与CMS配合工作最合适。该收集器在多cpu环境中效率比较高。

ParNew收集器

9.3、Parallel Scavenge收集器

该收集器是一个新生代收集器,采用复制算法,也是多线程收集器,和ParNew类似,被称为吞吐量优先收集器。但是Parallel Scavenge的关注点和其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集器时用户线程的停顿时间,而Parallel Scavenge的目的是达到一个可控制的吞吐量。

吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

停顿时间越短越适合需要与用户交互的程序,而高吞吐量可以高效率地利用cpu时间。

9.4、Serial Old收集器

Serial Old是Serial收集器老年代的版本,同样是一个单线程收集器,使用标记-整理算法。主要用于Client模式。在Server模式下,可以和Parallel Scavenge搭配使用,还可以作为CMS收集器的后备预案。

9.5、Parallel Old收集器

是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。jdk1.6后出现的,为了和Parallel Scavenge搭配使用。

9.6、CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于标记-清楚算法。适合于服务器高响应速度、低系统停顿时间的场合。

cms收集器的运行过程更复杂,整个过程分为4个步骤:初始标记、并发标记、重新标记、并发清除。其中初始标记和重新标记需要短暂停顿,其他过程不需要,因此总的停顿时间比较短。

3个缺点:对cpu资源敏感,cpu数量少时会拖慢用户程序的执行时间;无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致一次Full GC的产生(使用Serial Old收集垃圾),浮动垃圾是在并发标记过程中产生的;由于使用标记--清除算法实现,会导致大量空间碎片产生。分配大对象时可能会导致触发一次Full GC。

CMS收集器

9.7、G1收集器

G1(Garbage-First)是一款面向服务端应用的垃圾收集器。与前几个收集器相比,G1收集器有以下特点:

  • 并行与并发(通过多个cpu来缩短停顿的时间)
  • 分代收集(仍然保留了分代的概念,但不需要其他收集器配合就能独立管理整个GC堆)
  • 空间整合(整体上属于“标记-整理”算法,从局部(两个Region之间)上基于“复制”算法实现的,不会导致空间碎片)
  • 可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)

此外,G1收集器将Java堆划分为多个大小相等的Region(独立区域),新生代与老年代都是一部分Region的集合,G1的收集范围则是这一个个Region(化整为零)。G1只所有能建立可预测的停顿时间模型,是因为它有计划地避免在整个java堆中进行全区域的垃圾收集,这是通过维护一个优先列表实现的(Remembered Set)。

G1的工作过程如下:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

G1收集器

10、内存分配

前面讲的垃圾收集器都是关于如何回收内存的,接下来将对象分配的策略。一般的,对象是在堆上分配,主要分配在新生代(一般含有一个Eden区和两个survivor区)的Eden区上,如果启动了本地线程分配缓冲,则按线程优先在TLAB上分配。少数情况下可能会直接分配在老年代中。

  1. 大多情况下,对象在新生代Eden区分配。如果没有足够空间,虚拟机将发起一次Minor GC。
  2. 大对象,比如很长的字符串和数组,直接分配在老年代中。
  3. 虚拟机给每一个对象定义了一个年龄计数器,每Minor GC一次,年龄加一,到达一定程度会被放入到老年代。
  4. 即使年龄不够,也可能会晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代,无需达到指定年龄。
  5. 新生代将内存分为了大的Eden区和两个小的Survivor区,一个Eden区和一个survivor区用于存放对象,但进行GC时,如果Eden和survivor的存活对象大于另一个survivor区,则多余的将在老年区寄存,称为分配担保。如果老年代存不下,则进行一次Full GC来让老年代腾出更多空间。

11、Minor GC 和 Full GC

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

参考:

周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社

https://blog.csdn.net/u011080472/article/details/51324422

猜你喜欢

转载自blog.csdn.net/jdbdh/article/details/82529463