9159金沙游艺场

图片 18
(转)网页性能管理详解
图片 1
Java面试笔试-001-Java语言的优点

JVM的大管家:自动内存管理机制

3.3 The Parallel GC

Parallel收集器是Serial收集器的多线程版本,其使用多个线程在新生代收集垃圾。默认情况下,对于N核的处理器,Parallel收集器会使用N个线程来进行垃圾收集。收集器使用线程的数论可以通过如下命令配置:

-XX:ParallelGCThreads=<desired number>

如果是单核的处理器,即使配置为Parallel收集器,其实也是在采用单线程模式,通常双核以上的处理器才会使用Parallel收集器,以达到更好的性能。

使用场景Parallel收集器是一种基于吞吐量(throughput)考虑的收集器,因为其可以使用多线程来加快垃圾回收效率,减少应用等待时间,提高来应用的吞吐量。因此其适用于服务器端的虚拟机

-XX:+UseParallelGC使用该命令,JVM将为为新生代设置Parallel收集器来收集垃圾,而老年代会任然使用单线程模式。

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelGC -jar c:javademosdemojfcJava2DJava2demo.jar

如果希望新生代和老年代都使用多线程模式请使用如下命令:-XX:+UseParallelOldGC

Java2Demo:java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar c:javademosdemojfcJava2DJava2demo.jar

参考:

1、《JAVA编程思想》,第5章;

2、《Java深度历险》,Java垃圾回收机制与引用类型;

3、《深入理解Java虚拟机:JVM高级特效与最佳实现》,第2-3章;

4、Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

5、Java垃圾回收机制详解

6、Java系列笔记 – Java 内存区域和GC机制

   空间担保分配

和C语言手动管理内存不同,JVM实现了自动内存管理机制,这也是Java语言的一大特点。

四、四种引用状态

1、强引用

代码中普遍存在的类似”Object obj = new
Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

2、软引用

描述有些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。Java中的类SoftReference表示软引用。

3、弱引用

描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java中的类WeakReference表示弱引用。

4、虚引用

这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系。Java中的类PhantomReference表示虚引用。

图片 1

ThreadLocal中有强引用和弱引用的应用,并且有内存泄漏风险。Java并发:线程本地变量ThreadLocal

图片 2

老年代用来存储长时间存活的对象,JVM中会设定一个阈值,当新生代中的对象经过young
GC
而存活的次数达到该阈值后,这个对象就会被移动到老年代。同样老年代也会需要垃圾收集工作,引发老年代垃圾收集old
GC

3、标记-整理(Mark-Compact)算法

过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。

图片 3

总结:大批对象死去、少量对象存活的,使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的,采用标记-清理算法或者标记-整理算法。

Serial
Old
收集器为Serial的老年代版本,单线程、“标记-整理”算法,在Client模式下为虚拟机使用。

图片 4Heap
Parameters

五、垃圾收集算法

    生命周期较长的对象进入老年代

[图片上传中…(1513336330749.png-f3752c-1513438068985-0)]

2、复制算法

将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。

优点:这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。

缺点:对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。

HotSpot虚拟机采用这种算法来回收新生代。

图片 5

ParNew 收集器与Serial Collector唯一不同的就是Serial
Collector是单线程的,ParNew Collector是多线程的,ParNew Collector是JVM
Server模式中默认的新生代GC算法。

  1. 为对象分配合适的内存;
  2. 在合适的时机回收对象的内存;

六、垃圾收集器

HotSpo虚拟机垃圾收集器如图:

图片 6

1.如果两个收集器之间存在连线,那说明它们可以搭配使用。

2.虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器。

3.没有最好的垃圾收集器,更加没有万能的收集器,只能选择对具体应用最合适的收集器。

1、Serial收集器

新生代收集器,使用停止复制算法,使用一个线程进行GC,串行,其它工作线程暂停。

单线程串行:进行垃圾收集时必须暂停其他线程的所有工作,直到它收集结束为止。

在用户不可见的情况下要把用户正常工作的线程全部停掉(Stop The World)。

不过实际上到目前为止,Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器,因为它简单而高效。用户桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代停顿时间在几十毫秒最多一百毫秒,只要不是频繁发生,这点停顿是完全可以接受的。

使用-XX:+UseSerialGC可以使用Serial+Serial
Old模式运行进行内存回收(这也是虚拟机在Client模式下运行的默认值)

图片 7

2、ParNew收集器

新生代收集器,使用停止复制算法,用多个线程进行GC(Serial收集器的多线程版),并行,其它工作线程暂停,关注缩短垃圾收集时用户线程的停顿时间。

Server模式下的虚拟机首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作。

使用-XX:+UseParNewGC开关来控制使用ParNew+Serial
Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

图片 8

3、Parallel Scavenge收集器

新生代收集器,使用停止复制算法,多线程,并行,关注CPU吞吐量。

吞吐量=运行用户代码的时间/总时间,比如:JVM运行100分钟,其中运行用户代码99分钟,垃圾收集1分钟,则吞吐量是99%。反映CPU使用效率。

CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel
Scavenge收集器的目标则是打到一个可控制的吞吐量。

停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。

使用-XX:+UseParallelGC开关控制使用Parallel
Scavenge+Serial
Old收集器组合回收垃圾(这也是在Server模式下的默认值);使用-XX:GCTimeRatio来设置用户执行时间占总时间的比例,默认99,即1%的时间用来进行垃圾回收。使用-XX:MaxGCPauseMillis设置GC的最大停顿时间(这个参数只对Parallel
Scavenge有效)

用开关参数-XX:+UseAdaptiveSizePolicy可以进行动态控制,如自动调整Eden/Survivor比例,老年代对象年龄,新生代大小等,这个参数在ParNew下没有。

图片 9

4、Serial Old收集器——Serial收集器的老年代版本

5、Parallel Old收集器——Parallel Scavenge收集器的老年代版本

使用-XX:+UseParallelOldGC开关控制使用Parallel Scavenge +Parallel
Old组合收集器进行收集。

6、CMS收集器

老年代收集器,使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。以获取最短回收停顿时间为目标

使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial
Old进行内存回收,优先使用ParNew+CMS,当用户线程内存不足时,采用备用方案Serial
Old收集(悲观full gc)。

过程:

初始标记,标记GCRoots能直接关联到的对象,stop the world,时间很短。

并发标记,标记GCRoots可达的对象,和应用线程并发执行,不需要用户停顿,时间很长。

重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,stop
the world,时间较初始标记阶段长。

并发清除,回收内存空间,和应用线程并发执行,时间很长。

缺点:

需要消耗额外的CPU和内存资源,在CPU和内存资源紧张,CPU较少时,会加重系统负担(CMS默认启动线程数为/4)。

在并发收集过程中,用户线程仍然在运行,所以可能产生“浮动垃圾”,本次无法清理,只能下一次Full
GC才清理。

  
因此在GC期间,需要预留足够的内存给用户线程使用。所以使用CMS的收集器并不是老年代满了才触发Full
GC,而是在使用了一大半的时候就要进行Full GC。

  (默认68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction来设置)

   如果预留的用户线程内存不够,则会触发Concurrent
Mode Failure,此时将触发备用方案:使用Serial Old
收集器进行收集,但这样停顿时间就长了。

  
如果用户线程消耗内存不是特别大,可以适当调高-XX:CMSInitiatingOccupancyFraction以降低GC次数,提高性能。

CMS采用的是标记清除算法,会导致内存碎片的产生

   可以使用-XX:+UseCMSCompactAtFullCollection来设置是否在Full
GC之后进行碎片整理

  用-XX:CMSFullGCsBeforeCompaction来设置在执行多少次不压缩的Full
GC之后,来一次带压缩的Full GC

:Java系列笔记 – Java 内存区域和GC机制中对CMS做了详细介绍

7、G1收集器

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1收集器有以下特点:

并行+并发。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。

分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。

空间整合。基于标记 – 整理算法,无内存碎片产生。

可预测的停顿。能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

  在G1之前的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域,虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region的集合。

  
Java虚拟机规范对垃圾收集器的具体实现并没有任何规定,所以不同厂商、不同版本的虚拟机提供的垃圾收集器会有很大的不同。下面所介绍的收集器只是HotSpt1.7的垃圾收集器。

3.2 The Serial GC

Serial收集器是Java SE 5
/6中默认的客户端虚拟机的收集器。在Serial收集器中新生代和老年代都是通过线性过程来收集垃圾。垃圾收集的过程是Stop
the World
事件,所有其他的进程都必须暂停,至到其结束。

使用场景Serial收集器的特点是简单直接,适用于对于效率要求不高的客户端虚拟机上。就单个处理器的情况下,Serial收集器的效率是很高的,直到现在很多内存资源有限的嵌入式设备上都在使用Serial收集器。

此外如果物理机上存在大量的JVM在运行,使用Serial收集器让单个处理器收集垃圾效果会更高,因此这样会较少多个JVM之间的协调(因为所有的任务都停下了)。

启动Serial收集器

-XX:+UseSerialGC

使用例子

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar c:javademosdemojfcJava2DJava2demo.jar

1、标记-清除(Mark-Sweep)算法

首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。

不足:

  从效率的角度讲,标记和清除两个过程的效率都不高;

  从空间的角度讲,标记清除后会产生大量不连续的内存碎片。

  (内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作)

图片 10

 

2.2 老年代和标记整理算法

每次young
GC
后,存活的对象都会从一个Survivor复制到另一个Survivor,在复制的过程中JVM会记录下对象是经过多少次GC而存活的。

图片 115.
持续老化

当对象存活的次数达到一定阈值之后(JVM默认为15,本例为8),JVM就会将该对象从新生代转移到老年代保存。用户可以使用-XX:MacTenuringThreshold来设置该阈值

图片 126.
老化对象进入老年代

随着GC的不断进行,将会有更多的对象被移动到老年代。当老年代的内存被使用尽,也需要合适的算法来回收资源。

图片 137.
持续图片 148.
总结

复制算法在对象存活率较高时要执行较多的复制操作,效率会变低,因此不适合在老年区使用。HotSpot使用标记—整理算法来回收老年代内存。

标记—整理算法的核心思想是标记出需要回收的对象之后,不是直接清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
这样可以保证老年区有连续空间用以存储较大的数据。

标记—整理算法工作流程如下:

  • Step 1: 标记
    Marking
    首先JVM标记处哪些对象是在使用中的,那些是无用的图片 15标记图中蓝色为使用中的对象,橙色为无用待回收的对象。标记过程是一个较为耗时的操作。
  • Step 2: 删除并整理 Deletion with
    Compacting
    让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,加快清理的效率。图片 16整理

大多数情况下,old GC是由 young
GC
引起,这是因为JVM的空间分配担保机制的存在。

发生young
GC
时,JVM会检测之前每次晋升到老年代对象的平均大小是否大于老年代的剩余空间大小,若大于,说明对象晋级到老年代有内存不够的危险,则直接进行一次Full
GC
,也就是old GCyoung
GC
都执行,同时回收老年代和新生代的内存资源。若小于,则只会进行young
GC
,在新生代中回收内存。

因为新生代中的对象“朝生夕死”,young GC发动更为频繁,因此常常是young
GC来引发old GC

七、GC日志

[GC [DefNew: 310K->194K, 0.0269163 secs] 310K->194K, 0.0269513 secs] [Times: user=0.00 sys=0.00, real=0.03 secs] [GC [DefNew: 2242K->0K, 0.0018814 secs] 2242K->2241K, 0.0019172 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC  [Tenured: 2241K->193K, 0.0056517 secs] 4289K->193K, [Perm : 2950K->2950K], 0.0057094 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation   total 2432K, used 43K [0x00000000052a0000, 0x0000000005540000, 0x0000000006ea0000)  eden space 2176K,   2% used [0x00000000052a0000, 0x00000000052aaeb8, 0x00000000054c0000)  from space 256K,   0% used [0x00000000054c0000, 0x00000000054c0000, 0x0000000005500000)  to   space 256K,   0% used [0x0000000005500000, 0x0000000005500000, 0x0000000005540000) tenured generation   total 5312K, used 193K [0x0000000006ea0000, 0x00000000073d0000, 0x000000000a6a0000)   the space 5312K,   3% used [0x0000000006ea0000, 0x0000000006ed0730, 0x0000000006ed0800, 0x00000000073d0000) compacting perm gen  total 21248K, used 2982K [0x000000000a6a0000, 0x000000000bb60000, 0x000000000faa0000)   the space 21248K,  14% used [0x000000000a6a0000, 0x000000000a989980, 0x000000000a989a00, 0x000000000bb60000)No shared spaces configured.

1、日志的开头“GC”、“Full
GC”表示这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。如果有Full,则说明本次GC停止了其他所有工作线程(Stop-The-World)。看到Full
GC的写法是“Full GC”,这说明是调用System.gc()方法所触发的GC。

2、“GC”中接下来的“[DefNew”表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的,例如上面样例所使用的Serial收集器中的新生代名为“Default
New
Generation”,所以显示的是“[DefNew”。如果是ParNew收集器,新生代名称就会变为“[ParNew”,意为“Parallel
New Generation”。如果采用Parallel
Scavenge收集器,那它配套的新生代称为“PSYoungGen”,老年代和永久代同理,名称也是由收集器决定的。

3、后面方括号内部的“310K->194K”、“2242K->0K”,指的是该区域已使用的容量->GC后该内存区域已使用的容量。方括号外面的“310K->194K”、“2242K->2241K”则指的是GC前Java堆已使用的容量->GC后Java堆已使用的容量

4、再往后“0.0269163
secs”表示该内存区域GC所占用的时间,单位是秒。最后的“[Times: user=0.00
sys=0.00 real=0.03
secs]”则更具体了,user表示用户态消耗的CPU时间、内核态消耗的CPU时间、操作从开始到结束经过的墙钟时间。后面两个的区别是,墙钟时间包括各种非运算的等待消耗,比如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以如果看到user或sys时间超过real时间是完全正常的。

5、“Heap”后面就列举出堆内存目前各个年代的区域的内存情况。

如上图JVM把堆划分为Young、Old、Perm三大区域,对应着不能年龄的对象;然后又把Young分为:Eden、Survivor
From、Survivor To三小块;各个区域存放对象的区别如下:

根据上面提到分区管理的思想,JVM的堆中,除了方法区之外,其余内存堆被分为:

三、判断对象是否存活

通过引用计数法/可达性分析法来判定对象是否存活

1、引用计数法

给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。

Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。

2、可达性分析法

这个算法的基本思想是通过一系列称为“GC
Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,

当一个对象到GC Roots没有任何引用链(即GC
Roots到对象不可达)时,则证明此对象是不可用的。

可以作为GCRoots的对象包括下面几种:

   虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

   方法区中的类静态属性引用的对象。

   方法区中常量引用的对象。

   本地方法栈中JNI引用的对象。

图片 17

如图:obj8、obj9、obj10都没有到GCRoots对象的引用链,即便obj9和obj10之间有引用链,他们还是会被当成垃圾处理,可以进行回收。

3、finalize方法

对于可达性分析算法而言,若要判断一个对象死亡,需要经历两次标记阶段。

第一次标记:对象在进行可达性分析后发现没有与GCRoots相连的引用链,则该对象被第一次标记并进行一次筛选,筛选条件为是否有必要执行该对象的finalize方法

  若对象没有覆盖finalize方法或者该finalize方法是否已经被虚拟机执行过了,则均视作不必要执行该对象的finalize方法,即该对象将会被回收。

  若对象覆盖了finalize方法并且该finalize方法并没有被执行过,这个对象会被放置在一个叫F-Queue的队列中,之后会由虚拟机自动建立的、优先级低的Finalizer线程去执行

第二次标记:对F-Queue中对象进行第二次标记

  如果对象在finalize方法中拯救了自己,即关联上了GCRoots引用链,那么在第二次标记的时候该对象将从“即将回收”的集合中移除

  如果对象还是没有拯救自己,那就会被回收

如下代码演示了一个对象如何在finalize方法中拯救了自己,然而,它只能拯救自己一次,第二次就被回收了。具体代码如下:

package com.demo;/* * 此代码演示了两点: * 1.对象可以再被GC时自我拯救 * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次 * */public class FinalizeEscapeGC {        public String name;    public static FinalizeEscapeGC SAVE_HOOK = null;    public FinalizeEscapeGC(String name) {        this.name = name;    }    public void isAlive() {        System.out.println("yes, i am still alive :)");    }        @Override    protected void finalize() throws Throwable {        super.finalize();        System.out.println("finalize method executed!");        System.out.println(this);        FinalizeEscapeGC.SAVE_HOOK = this;    }    @Override    public String toString() {        return name;    }    public static void main(String[] args) throws InterruptedException {        SAVE_HOOK = new FinalizeEscapeGC("leesf");        System.out.println(SAVE_HOOK);        // 对象第一次拯救自己        SAVE_HOOK = null;        System.out.println(SAVE_HOOK);        System.gc();        // 因为finalize方法优先级很低,所以暂停0.5秒以等待它        Thread.sleep(500);        if (SAVE_HOOK != null) {            SAVE_HOOK.isAlive();        } else {            System.out.println("no, i am dead : (");        }        // 下面这段代码与上面的完全相同,但是这一次自救却失败了        // 一个对象的finalize方法只会被调用一次        SAVE_HOOK = null;        System.gc();        // 因为finalize方法优先级很低,所以暂停0.5秒以等待它        Thread.sleep(500);        if (SAVE_HOOK != null) {            SAVE_HOOK.isAlive();        } else {            System.out.println("no, i am dead : (");        }    }}

运行结果如下:
leesfnullfinalize method executed!leesfyes, i am still alive :)no, i am dead : (

    Eden优先分配

当然old GC也是Stop the World事件,但是old GCyoung
GC
要慢很多,因为老年代中对象的存活率很高,要保存的对象很多。

一、简介

Java GC(Garbage Collection,垃圾回收)机制,是Java与C++/C的主要区别之一

  在C++/C语言中,程序员必须小心谨慎地处理每一项内存分配,且内存使用完后必须手工释放曾经占用的内存空间。当内存释放不够完全时,即存在分配但永不释放的内存块,就会引起内存泄漏,严重时甚至导致程序瘫痪。

  Java
语言的一大特点就是可以进行自动垃圾回收处理,而无需开发人员过于关注系统资源。

  垃圾回收机制对JVM中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver
Stop)的保证JVM中的内存空间,防止出现内存泄露和溢出问题。

问题:

  1、垃圾回收并不会按照程序员的要求,随时进行GC。

  2、垃圾回收并不会及时的清理内存,尽管有时程序需要额外的内存。

  3、程序员不能对垃圾回收进行控制。

作为Java程序员我们很难去控制JVM的内存回收,只能根据它的原理去适应,尽量提高程序的性能。

    HotSpot堆的瓜分

图片 18
Default Arrangement of Generations, Except for Parallel Collector and G1

二、垃圾回收过程

Minor GC/Young GC:只收集新生代的GC  触发条件:Eden区满时

Major GC/Full GC:收集老年代、永久带(根据垃圾收集器不同可能收集新生代)

触发条件: 

  调用System.gc()时,系统建议执行Full GC,但是不必然执行

  老年代空间不足

  方法去空间不足

  通过Minor GC后进入老年代的平均大小大于老年代的可用内存

  由Eden区、From Space区向To Space区复制时,对象大小大于To
Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

新生代垃圾回收过程

算法:“停止-复制”算法进行清理

1.创建对象分配到Eden区

2.第一次Eden区满,执行Minor
GC,将存活对象复制到一个存活区Survivor0,清空Eden区

  *此时,Survivor1是空白的,两个Survivor总有一个是空白的

  *Eden区是连续的内存空间,因此在其上分配内存极快

3.程序继续创建对象分配到Eden区,Eden区满执行Minor
GC,Eden和Survivor0中存活的对象复制到Survivor1,清空Eden和Survivor0

  *如果对象比较大,比如长字符串或大数组,Young空间不足,则大对象会直接分配到老年代上

  *大对象可能触发提前GC,应少用,更应避免使用短命的大对象

  *用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上

  *Eden和Survivor0存活对象占用空间超过Survivor1,多余对象进入老年代

  *由于绝大部分的对象都是短命的,甚至存活不到Survivor中,所以,Eden区与Survivor的比例较大,HotSpot默认是
8:1。

    如果一次回收中,Survivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到
老年代。

    用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor1:Survivor2=8:1:1.

4.程序继续创建对象分配到Eden区,Eden区满执行Minor
GC,Eden和Survivor1中存活的对象复制到Survivor0,清空Eden和Survivor1

5.当两个存活区切换了一定次数之后,仍然存活的对象,将被复制到老年代

  *HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制

老年代、永久代垃圾回收过程

算法:老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。

  
一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象,将所有存活的对象向一端移动,以保证内存的连续。

1.调用System.gc() 或老年代空间不足 或永久代空间不足时,执行Major GC

2.老年代对象引用新生代对象的情况,如果需要执行Young
GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。

  解决的方法是,年老代中维护一个512 byte的块——”card
table“,所有老年代对象引用新生代对象的记录都记录在这里。

  Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。

3.Minor GC与Full GC:

Minor GC时,检查进入老年代的大小是否大于老年代的剩余空间大小

大于:直接触发一次Full GC

不大于:查看是否设置了-XX:+HandlePromotionFailure

    允许:只会进行MinorGC,此时可以容忍内存分配失败

    不允许:进行Full GC

(如果设置-XX:+Handle
PromotionFailure不允许担保失败,则触发MinorGC就会同时触发Full
GC,哪怕老年代还有很多内存,所以,最好不要这样做)

4.方法区回收:

  常量池中的常量:没有引用了就可以被回收。(JDK7+以后,字符串常量被移动到堆中)

  无用的类信息:

    类的所有实例都已经被回收

    加载类的ClassLoader已经被回收

    类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。HotSpot提供-Xnoclassgc进行控制,使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以查看类加载和卸载信息-verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;-XX:+TraceClassUnLoading需要fastdebug版HotSpot支持

 

目前JVM
HotSpot中的实现方法是“分别对待,因类而异”,即针对不同生命周期的对象分配在不同内存区域,采取不同的方法回收内存垃圾。

Serial 收集器是比较古老的一种收集器,不过到现在他还是JVM
client模式中默认新生代的GC算法,
Serial是单线程的,它在进行垃圾回收工作时会暂停所有其他工作,Sum称为:“Stop
The
World”,直到回收任务结束,然后Serial工作时占用的时间比较多但它比较简单新生代内存不大的情况下回收工作时间还是短到可以接收的,基本一秒以内。

当新生代被充满时,会引发新生代垃收集young GC
因为新生代中对象生命周期都很短,所以young
GC
一般都能回收大量的空间,且会很快执行完毕。在young
GC
之后保留下来的对象将会保存在survivor区,这也是survivor名字的意义所在。

在初始标记、重新标记的时候会“Stop The World”,初始标记标记出GC
Roots能直接关联到的对象,并发标记进行GC Roots
Tracing,重新标记修复在程序继续运行导致标记的变动,CMS也有不好的地方就是CMS会占用较多的CPU由于CMS是使用标记清除算法实现的,所以可能会导致较多的碎片。

  • 新生代The Young Generation
  • 老年代The Old/Tenured Generation

年轻代在发生Minor
GC时,虚拟机会检测每次晋升到老年代的平均大小是否大于老年代剩余的存储空间,比老年代剩余空间大则改为直接Full
GC,如果小,则看HandlePromotionFailure设置是否允许担保失败,是则只会进行Minor
GC,否则也要改为进行一次Full GC。

2.3 如何确定可回收对象

到这里读者应该清楚JVM的堆中是如何为对象分配内部,并自动回收它们的了,但是还有一个疑问没有解决,那就是如何判定一个对象是无用的,应该被回收的。最朴素和高效的方法莫过于引用计数算法,其算法原理为:给对象添加一个引用计数器,每有一个地方引用他时,计数器值加1;当引用失效时,计数器值减1;任何时刻计数器值都为0的对象就是不可能再被使用的,也就是无用的。可是,引用计数算法它难解决对象之间的相互循环引用问题。

JVM中所使用的算法是可达性分析算法,其基本思路为:通过一系列的名为GC
Roots
的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC
Roots没有任何引用链相连时,则证明此对象是不可用的。

换而言之,GC
Roots对象是JVM一定在使用的对象,在其引用链上对象也就是在使用中的对象。
Java中可作为GC Roots的对象包括两大类:

  • 栈中引用的对象
    • 虚拟机栈(栈帧中的本地变量表)中的引用的对象;
    • 本地方法栈中JNI的引用的对象。
  • 方法区中的对象
    • 方法区中的类静态属性引用的对象;
    • 方法区中的常量引用的对象;

因为是依据引用来判断对象是否在使用中,为了更高效的回收对象,Java设计了四种引用:

  • 1)强引用
    在程序代码中普遍存在的,类似Object obj = new Object这类的引用。只要强引用还存在,则垃圾收集器永远不会回收掉被引用的对象。
  • 2)软引用 SoftReference
    一些还有用,但并非必须的对象。对软引用关联着的对象,在系统将要发生内存溢出前,会把这些对象列进回收范围中并进行第二次回收。若这次回收还是没有足够的内存,才会抛出内存溢出异常。
  • 3)弱引用 WeakReference
    非必须对象,强度比软引用更弱一些,被若引用关联的对象只能生存到下一次垃圾回收之前。
  • 4)虚引用 PhantomReference
    最弱的一种引用关系。无法通过虚引用来取得一个对象实例。为对象设置虚引用的唯一目的是希望在对象被垃圾收集器回收时收到一个系统通知。

对象中的finalize方法会影响对象的回收。GC中,如果对象没有在GC
Roots的引用链中,将会被第一次标记,如果当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,则说明finalize()方法已经没必要执行了,JVM会直接将其回收掉。

反之,若对象被判定为有必要执行finalize方法,那么对象将会被放置在一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行;然后,JVM会对F-Queue的队列中再进行第二次标记,看该对象是否任然是无用的,如果该对象重新与引用链上的任何一个对象建立关联即可,那么第二次标记时将被移除出“即将回收”的集合,反之,则会回收该对象。由此可见finalize()只会被执行一次,第二次对象面临回收时就会被直接回收,而忽视finalize()方法。

finalize()方法是Java刚诞生时的一种妥协产物,已经不推荐使用了。

现在我们已经知道了JVM内存分配和垃圾收集算法的工作原理,下面看看垃圾收集算法的实现,并根据使用场景选择合适垃圾收集器。

Sun有对各个区域给出建议的大小,Young区域为整个堆的1/4,Young中的Survivor为Young区域的1/8,安装JDK的时候有自带了visualvm工具,可以安装Visual
GC插件来查看到JVM各个区域的垃圾回收情况,可以看到内存大小、GC时间、已使用大小、剩余大小等等信息如图:

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图