java list clear 垃圾回收_Java垃圾回收

1.标记-清除算法

这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。

复制算法

这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,虚拟机生成的新对象则放在另一半空间中。垃圾回收器运行时,它把可到达对象复制到另一半空间,没有被复制的的对象都是不可达对象,可以被回收。这种方法适用于短生存期的对象,持续复制长生存期的对象由于多次拷贝,导致效率降低。缺点是只有一半的虚拟机空间得到使用。

标记-整理算法

分代收集算法

在实际中用到的回收器都是这几种算法的组合,比如从VisualVM中看到的内存是这样的(需要明白各部分都是怎样互相配合的):

6398676.html

整体上来看是分代收集算法,而S0、S1这两部分可以看做是标记-整理算法。

年轻代对象因为生命周期短,每次有约90%以上对象的占用空间被回收,采用“复制-清除”算法清理,具体过程:将新生代分为一个Eden空间和两个Survivor空间,默认Eden空间和Survivor空间的比例为8:1,对象分配到Eden和其中一个Survivor空间,回收时将存活的对象复制到另一个Survivor空间,然后将Eden空间和先前使用的Survivor空间清理。

老年代因对象生命周期较长,每次回收只有少部分对象没清理,如果使用“复制-清理”算法的话需要额外预留更多的空闲空间用于复制生存对象,

( 例如,100M的老年代占用空间 每次能回收50% ,那么他需要预留50M的空间 内存使用上不经济 )

所以回收时使用“标记-整理”算法。

那么第三个问题:常见的CMS垃圾回收器的执行流程是怎样的?

1.     初始标记:GC Roots直接关联的对象。

2.     并发标记:Root Tracing。

3.     重新标记:修复由于程序运行导致标记产生变动。

4.     并发清除

具体如下图所示:

6398676.html

可以看到只有在初始标记和重新标记的时候才需要Stop The World,其他都是和用户线程一起执行,不要以为这就完美了,并行执行的过程会消耗掉一些CPU资源。

Garbage-First,面向服务器的垃圾收集器,主要针对配置多处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

可以像CMS收集器一样,GC操作与应用的线程一起并发执行

紧凑的空闲内存区且没有很长的GC停顿时间

需要可预测的GC暂停耗时

不想牺牲太多吞吐量性能

启动后不需要请求更大的Java堆

G1是一款压缩型的收集器,G1通过有效的压缩完全避免了对细微空闲内存的分配,不用依赖于regions,大大简化了收集器,还消除了潜在的内存碎片问题。

G1执行垃圾回收的处理方式与CMS相似. G1在全局标记阶段(global marking phase)并发执行, 以确定堆内存中哪些对象是存活的。标记阶段完成后,G1就可以知道哪些heap区的empty空间最大。它会首先回收这些区,通常会得到大量的自由空间. 这也是为什么这种垃圾收集方法叫做Garbage-First(垃圾优先)的原因。顾名思义, G1将精力集中放在可能布满可收回对象的区域, 可回收对象(reclaimable objects)也就是所谓的垃圾. G1使用暂停预测模型(pause prediction model)来达到用户定 义的目标暂停时间,并根据目标暂停时间来选择此次进行垃圾回收的heap区域数量

被G1标记为适合回收的heap区将使用转移(evacuation)的方式进行垃圾回收. G1将一个或多个heap区域中的对象拷贝到其 他的单个区域中,并在此过程中压缩和释放内存. 在多核CPU上转移是并行执行的(parallel on multi-processors), 这样能减少 停顿时间并增加吞吐量. 因此,每次垃圾收集时, G1都会持续不断地减少碎片, 并且在用户给定的暂停时间内执行. 这比以前 的方法强大了很多. CMS垃圾收集器(Concurrent Mark Sweep,并发标记清理)不进行压缩. ParallelOld 垃圾收集只对整个堆 执行压缩,从而导致相当长的暂停时间。

需要强调的是, G1并不是一款实时垃圾收集器(real-time collector). 能以极高的概率在设定的目标暂停时间内完成,但不保证 绝对在这个时间内完成。 基于以前收集的各种监控数据, G1会根据用户指定的目标时间来预估能回收多少个heap区. 因此, 收集器有一个相当精确的heap区耗时计算模型,并根据该模型来确定在给定时间内去回收哪些heap区.

注意 G1分为两个阶段: 并发阶段(concurrent, 与应用线程一起运行, 如: 细化 refinement、标记 marking、清理 cleanup) 和 并行阶段(parallel, 多线程执行, 如: 停止所有JVM线程, stop the world). 而 FullGC(完整垃圾收集)仍然是单线程的, 但如果进 行适当的调优,则应用程序应该能够避免 full GC。

S Concurrent Mark Sweep并发标记清理收集器,回收年老代内存。

吞吐量      应用花在非GC上的时间百分比

GC负荷    与吞吐量相反,指应用花在GC上的时间百分比

暂停时间   应用花在GC stop-the-world 的时间

GC频率     顾名思义

Footprint   一些资源大小的测量,比如堆的大小

反应速度   从一个对象变成垃圾道这个对象被回收的时间

一个交互式的应用要求暂停时间越少越好,然而,一个非交互性的应用,当然是希望GC负荷越低越好。

一个实时系统对暂停时间和GC负荷的要求,都是越低越好。

一个嵌入式系统当然希望Footprint越小越好。

整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

1. 堆大小设置

JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。

2. 典型设置 java -Xmx3550m -Xms3550m -Xmn2g -Xss128k

-Xmx3550m:设置JVM最大可用内存为3550M。

-Xms3550m:设置JVM促使内存为3550m。

此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。

更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。

但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -

XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。

设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,

则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxPermSize=16m:设置持久代大小为16m。

-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,

则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。

如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,

这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

3.  参数信息

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

·            -XX:+PrintGC 输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]

[Full GC 121376K->10414K(130112K), 0.0650971 secs]

·            -XX:+PrintGCDetails 输出形式:

[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

·            -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息

·             -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。

·            -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

·            -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。

并行收集线程数。

·            -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

·            -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

·            -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

·            -Xloggc:filename

·            -XX:+UseSerialGC:设置串行收集器

·            -XX:+UseParallelGC:设置并行收集器

·            -XX:+UseParalledlOldGC:设置并行年老代收集器

·            -XX:+UseConcMarkSweepGC:设置并发收集器

·            -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:设置持久代大小

1.         年轻代大小选择

·            响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

·            吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

2.         年老代大小选择

·            并发垃圾收集信息

·            持久代并发收集次数

·            传统GC信息

·            花在年轻代和年老代回收上的时间比例

·            响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:减少年轻代和年老代花费的时间,一般会提高应用的效率

·            吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

3.         较小堆引起的碎片问题

因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:

·            -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。

·            -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

6398676.html

GC打印时间: [垃圾回收类型回收时间: [收集器名称: 年轻代回收前占用大小->年轻代回收后占用大小(年轻代当前容量),

年轻代局部GC时JVM暂停处理的时间] 堆空间GC前占用的空间->堆空间GC后占用的空间(堆空间当前容量)

,GC过程中JVM暂停处理的时间]。

垃圾回收类型:分为GC和Full GC.

GC一般为堆空间某个区发生了垃圾回收,

Full GC基本都是整个堆空间及持久代发生了垃圾回收,通常优化的目标之一是尽量减少GC和Full GC的频率。

收集器名称:一般都为收集器的简称或别名,通过收集器名称基本都能判断出那个区发生了GC。

代码执行

把Java源码丢给JVM肯定是不能执行的,需要先用javac编译成class文件才行,那么第一个问题:class文件的结构是怎样的?

·         常量池

·         访问标志

·         类索引、父类索引和接口索引

·         字段表

·         方法表

·         属性表

虚拟机规范并没有规定在什么时候要加载类,但是规定了在遇到new、反射、父类、Main的时候需要初始化完成。

JVM将类加载过程划分为三个步骤:装载、链接和初始化。

装载(Load):装载过程负责找到二进制字节码并加载至JVM中,JVM通过类的全限定名(com.bluedavy. HelloWorld)及类加载器(ClassLoaderA实例)完成类的加载;

链接(Link):链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量及解析类中调用的接口、类;

初始化(Initialize):执行类中的静态初始化代码、构造器代码及静态属性的初始化。

解释执行的好处是下载后启动速度快,但是缺点也非常明显:运行速度慢。

JIT正是用来解决这个问题的,能够将多次调用的方法、多次执行的循环体编译成本地代码。

优化是个很好玩的题目,记得在参加一次变成比赛的时候用gcc -O3编译之后的代码把printf()都没输出了。。在JIT中比较常见的优化手段有:

在HotSpot虚拟机中,对象在内存中存储的布局可以分为对象头包括两部分信息,第一部分

任何对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行。反对使用原因:代价高昂,不确定性大,如果发生死循环可能导致整个内存回收系统崩溃。

回收方法区(HotSpot虚拟机中的永久代):很多人认为方法去是没有垃圾收集的

主要回收两部分内容:废弃敞亮和无用的类

怎样的类可以被回收:

该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;

加载该类的ClassLoader已经被回收

该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值