Java JVM详解--通俗易懂教程

JVM:Java虚拟机的简称。

谈到JVM,通常会聊到三个问题:

1. 什么时候触发Java GC?

2. 对什么东西进行Java GC?

3. 如何进行Java GC?

首先解决第一个问题:

1. 什么时候触发Java GC?

GC分为minor GC和Full GC。

Full GC:

  1. 年老代被写满;
  2. 持久代被写满
  3. System.gc被显示调用
  1. 当有新对象生成的时候,如果申请eden空间失败的话,就会触发mino gc,对eden去进行gc处理,清除非存活的对象,并且把尚且存活的对象移到survival区里面。经过一定次数的对象进入老年代的时候,如果老年代的剩余空间放不下升到老年代的对象的时候,就会触发full gc,利用标记清除算法,将不可达的对象清楚,或者小于的时候被handlepromotionFailure参数强制full gc。具体的gc时间是由系统决定的,无法预测。

2. 对什么东西进行GC ?对GC root搜索不到,并且经过第一次标记,清除之后,仍然没有复活的对象进行GC。

3. 做了什么工作,怎么进行GC?

主要做了清理对象,整理内存的工作,Java堆中主要是有新生代和年老代,他们采用不同的回收方式,例如新生代采用了标记复制的算法(因为其生存时间比较短),新生代进行gc的时候,会把eden区存活的对象放到另外一个survival区域里面,然后把eden区和另外一个survival区清除。而年老代采用了标记清除的算法,首先标记出存活的对象,然后移到另一端,这样也能减少内存碎片化。

垃圾收集有哪些算法?

标记清除算法:标记出需要回收的对象,标记完成后,统一回收所有被标记的对象。(两个不足:效率不高:标记和清除都不高;空间问题:产生大量不连续的内存碎片,可能导致以后在分配较大对象的时候因无法找到连续的内存而触发GC)

复制算法:(新生代)比较适合对象存活率比较低的场景。应用于新生代,它主要是把内存分成两个块,每次只使用一块,就像新生代一样,有两个区域:一个是eden区,一个是survival区。这样也就解决了内存碎片化的问题。

标记整理算法:(老年代)比较适合对象存活率比较高的场景,应用于老年代,将所有存活对象都移向另一端,然后直接清除掉端边界外的内存。

如何判断对象是否存活?

引用计数法:每个对象都有一个引用计数器,当对象被引用一次的时候,计数器+1,当对象引用失效的时候,计数值-1,实时性,当对象的引用计数器的值为0,则立刻回收,不能解决循环引用的问题。

可达性算法分析:从GC Root作为起点开始搜索,,那么整个连通图的对象都是存活的对象,对于GC Root无法到达的对象便成了垃圾回收的对象。(解决循环引用的问题)

常用的引用:

强引用>软引用>弱引用>虚引用

强引用:GC永远都不会回收的对象。内存空间不足时,宁愿抛出OutOfMemoryError。

软引用:内存空间不足时会考虑回收它,空间足够的时候不会

弱引用:不管内存空间够不够,都会回收它

虚引用:不会影响生存时间,目的是能在这个对象被收集器回收时收到一个系统通知

  1. 哪些对象可以作为GC Root对象

虚拟机栈中的对象

方法区的静态变量

方法区常量池的对象

  1. JVM垃圾收集器:

分为新生代收集器,老年代收集器。

新生代收集器:

在执行机制上JVM主要提供了串行GC(serial GC),并行GC(ParNew),并行回收GC(parallel scavenge)

Serial GC:在整个GC的过程中采用单线程的方式来进行垃圾回收,在回收过程中,必须停止其他所有的工作线程。 适用于单CPU,是client模式下默认的GC方式。

parNew :其实就是serialGC的多线程版本,除了使用多条线程来进行垃圾收集之外,其他行为跟serialGC差不多。,是server模式下默认使用的GC方式。能够与CMS收集器配合工作。

Parallel scavenge:它跟parNew差不多,也是一个并行的多线程收集器。不过他的关注点跟其他收集器不一样。Cms等收集器的关注点是尽可能的缩短垃圾收集是用户线程的停顿时间,而parallel scavenge的目的是为了达到一个可控制的吞吐量。(也就是CPU用于运行用户代码的时间与CPU总的消耗时间的比值)还有一个就是parallel scavenge有GC的自适应调节策略:我们可以通过一个参数,这个参数叫做userAdaptiveSizePolicy来让虚拟机帮我们动态的调整这些参数以提供最合适的停顿时间或者最大的吞吐量。

老年代收集器:

串行GC(serial old):他是serial的老年代版本,也是一个单线程收集器,使用标记整理算法。

并行GC(parallel old):是parallel scavenge 的老年代版本。使用多线程和“标记-整理”算法。

并发GC(CMS):获取最短回收停顿时间为目标的收集器。

优点是:并发收集,低停顿

总结如下:

  1. 初始标记:标记一下GC root能够直接关联到的对象,速度很快
  2. 并发标记:进行GC root tracing的过程,耗时比较长,并发,
  3. 重新标记:为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的的标记记录。
  4. 并发清除:耗时较长,并发。
  5. 这四个过程中,耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,整体上说:cms收集器的回收过程时与用户线程一起工作的。

缺点有三:

  1. 对cpu资源敏感,因为并发标记和并发清除过程中会占用cpu,公式是:

(cpu数量+3)/4,当cpu<4的时候,占用了不少于25%的cpu资源。(增量式并发收集器:让他们交替执行)

  1. 无法处理浮动垃圾:在第四个阶段并发清理的时候,因为用户线程还在运行,所以就会有新的垃圾产生,这些就是浮动垃圾,cms是无法在当次收集中处理掉他们的。(运行期间要预留内存给他们,不然会出现concurrent mode failure)
  2. 因为他是基于标记-整理算法进行清除的,所以将会产生内存碎片化的问题,这将给大对象分配带来麻烦。

成熟的收集器:

G1:G1收集器是当今收集器技术发展最前沿的成果,他是一款面向服务端应用的收集器。

过程如下:

  • 初始标记:标记一下GC root能直接关联到的对象。,需要停顿线程,耗时短。
  • 并发标记:从gc root开始对堆中的对象进行分析,找出存活的对象,耗时长,但是可以和用户线程并发执行。
  • 最终标记:为了修正在并发标记过程中因用户程序继续运作而导致标记产生变动的那一部分标记记录。这个阶段需要停顿用户线程。
  • 筛选回收:首先对各个region的回收价值和成本进行排序,根据用户所期望的停顿时间来指定回收计划。

优势:

  1. 并行与并发:G1能够充分利用CPU,多核环境下硬件的优势,使用多个CPU来缩短stop-the-world停顿时间。
  2. 分代收集:与其他收集器一样,分代概念在G1仍然保存。
  3. 空间整合:与cms的“标记-清除算法”不同,G1整体上看来是基于“标记-整理的算法”来实现的收集器,这意味着在G1运行期间不会产生内存空间碎片。。
  4. 可预测停顿:这是G1相对于CMS的一大优势,降低低停顿时间是cms和G1共同的关注点,但是G1还能建立可预测的停顿时间模型,让使用者明确指定在一个时间为M的时间片段内,消耗在垃圾收集的时间不得超过N毫秒。

垃圾收集器参数总结:

userSerialGC:虚拟机运行在client模式下的默认值,打开此开关之后,使用serial+serial old组合收集器。

userParNewGC:打开此开关之后,使用parNew+serial Old组合收集器。

userConcMarkSweepGC:打开此开关之后,使用parNew+CMS+serial Old组合垃圾收集器,serial old作为后备收集器使用。(concurrent mode failure)

userParallelGC:server模式下的默认值,parallel Scavenge+serial old组合垃圾收集

userParallelOldGC:parallel scavenge+parallel old收集器组合收集。

SurvivalRadio:eden:survival= 8

userAdaptiveSizePolicy:GC自适应调节策略,调整Java堆各区域的大小以及进入老年代的年龄。

handlePromotionFailure:是否允许分配担保失败

ParallelGCThreads:设置并行GC时进行内存回收的线程数。

猜你喜欢

转载自blog.csdn.net/weixin_37766296/article/details/83585825