随着程序的运行,内存中存在的实例对象、变量等信息占据的内存越来越多,如果不及时进行垃圾回收,必然会带来程序性能的下降,甚至会因为可用内存不足造成一些不必要的系统异常。
如果某个对象已经不存在任何引用,那么它可以被回收。
每个对象添加一个引用计数器,每被引用一次,计数器加1,失去引用,计数器减1,当计数器在一段时间内保持为0时,该对象就认为是可以被回收得了。(在JDK1.2之前,使用的是该算法)
缺点:当两个对象A、B相互引用的时候,当其他所有的引用都消失之后,A和B还有一个相互引用,此时计数器各为1,而实际上这两个对象都已经没有额外的引用了,已经是垃圾了。但是却不会被回收
该算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点
目前java 中可作为GC Root 的对象有:
JDK 1.2之后,对引用进行了扩充,引入了强、软、若、虚四种引用,被标记为这四种引用的对象,在GC时分别有不同的意义:
就是为刚被new出来的对象所加的引用,它的特点就是,永远不会被GC,除非显示的设置null,才会GC。代码如下:
Object ojb = new Object();
非必须引用,内存溢出之前进行回收。如果JVM内存并不紧张,这类对象可以不被回收,如果内存紧张,则会被回收。此处有一个问题,既然被引用为软引用的对象可以回收,为什么不去回收呢?其实我们知道,Java中是存在缓存机制的,就拿字面量缓存来说,有些时候,缓存的对象就是当前可有可无的,只是留在内存中如果还有需要,则不需要重新分配内存即可使用,因此,这些对象即可被引用为软引用,方便使用,提高程序性能。代码如下:
Object obj = new Object();
SoftReference
obj =null;
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
第二次垃圾回收时回收。代码如下:
Object obj = new Object();
WeakReference
obj =null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器
垃圾回收时回收,无法通过引用取到对象值。代码如下
Object obj = new Object();
PhantomReference
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。虚引用主要用于检测对象是否已经从内存中删除。
扩展:如果一个对象的引用有多个,怎么通过可达性确定回收周期呢?两个原则:
内存主要被分为三块,新生代、旧生代、持久代。三代的特点不同,造就了他们所用的GC算法不同,新生代适合那些生命周期较短,频繁创建及销毁的对象,旧生代适合生命周期相对较长的对象,持久代在Sun HotSpot中就是指方法区(有些JVM中根本就没有持久代这中说法)。首先介绍下新生代、旧生代、持久代的概念及特点:
新生代:New Generation或者Young Generation。上面大致分为Eden区和Survivor区,Survivor区又分为大小相同的两部分:FromSpace 和ToSpace。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代的大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例.
旧生代:Old Generation。用于存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象。旧生代占用大小为-Xmx值减去-Xmn对应的值。
持久代:Permanent Generation。在Sun的JVM中就是方法区的意思,尽管有些JVM大多没有这一代。主要存放常量及类的一些信息默认最小值为16MB,最大值为64MB,可通过-XX:PermSize及-XX:MaxPermSize来设置最小值和最大值。
就是分为标记和清除两个阶段进行处理内存中的对象
缺点:
java中新生代的from和to空间就是使用这个算法
优点:这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等。复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
缺点:复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低(所以eden区没有采用这个算法)
是在标记清除法基础上做了优化,把存活的对象压缩到内存一端,然后直接清理掉端边界以外的内存(老年代使用的就是标记压缩法)
1、根据对象存活周期的不同将内存划分为几块。
2、一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3、在新生代中,每次垃圾收集时都发现有大批对象死去(回收频率很高),只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
其中,新生代又细分为三个区:Eden,From Survivor,ToSurviver,比例是8:1:1
4、老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
其主要就是将整个内存分为N个多小的独立空间,每个小空间都可以独立使用,这样细粒度的控制一次回收都少个小空间和那些个小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。
评价一个垃圾收集GC算法的两个标准
JVM在专门的线程[GC Threads]中执行GC 只要GC线程是活动的 就会和应用程序线程[Application Threads]争用当前可用CPU的时钟周期而吞吐量就是指应用程序线程占程序总用时的比例
一个时间段内应用程序线程让GC线程执行而完全暂停。
垃圾回收器的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以高效的执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于更好地标记垃圾对象,因此垃圾回收时,都会产生应用程序的停顿
HotSpot JVM收集器
上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。
使用单线程进行垃圾回收的收集器,每次回收时,串行收集器只有一个工作线程,对于并行能力较弱的计算机来说,串行收集器的专注性和独占性往往有更好的性能表现。串行收集器可以在新生代和老年代中使用,根据作用于不同的堆空间,分为新生代串行收集器和老年代收集器。
-XX:+UseSerialGC
:年轻串行(Serial),老年串行(Serial Old)
Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
1、Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
2、主要意义也是在于给Client模式下的虚拟机使用。
3、如果在Server模式下,那么它主要还有两大用途:
一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用[1],
另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
1、Serial收集器的多线程版本
2、单CPU不如Serial,因为存在线程交互的开销
-XX:+UseParNewGC
新生代并行(ParNew),老年代串行(Serial Old)
-XX:ParallelGCThreads=n
设置并行收集器收集时使用的CPU数。并行收集线程数。一般最好和计算机的CPU相当
-XX:+UseParallelGC
新生代使用并行回收收集器,老年代使用串行收集器
1、吞吐量优先”收集器
2、新生代收集器,复制算法,并行的多线程收集器
3、目标是达到一个可控制的吞吐量(Throughput)。
4、吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
5、两个参数用于精确控制吞吐量:-XX
:MaxGCPauseMillis
是控制最大垃圾收集停顿时间
-XX
:GCTimeRatio
直接设置吞吐量大小
-XX
:+UseAdaptiveSizePolicy
动态设置新生代大小、Eden与Survivor区的比例、晋升老年代对象年龄
6、并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
7、并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
-XX:+UseParallelOldGC
新生代和老年代都使用并行回收收集器
1、Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
2、在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
1、以获取最短回收停顿时间为目标的收集器。
2、非常符合互联网站或者B/S系统的服务端上,重视服务的响应速度,希望系统停顿时间最短的应用
3、基于“标记—清除”算法实现的
4、CMS收集器的内存回收过程是与用户线程一起并发执行的
5、它的运作过程分为4个步骤,包括:
6、优点:并发收集、低停顿
7、缺点:
-XX:+UseConcMarkSweepGC
应用CMS收集器
-XX:ConcGCThreads
设置并发线程数量
-XX:CMSInitiatingOccupancyFraction
设置当老年代空间实用率达到百分比值时进行一次cms回收,默认为68,当老年代的空间使用率达到68%的时候,会执行CMS回收
如果内存使用率增长的很快,在CMS执行的过程中,已经出现了内存不足的情况,此时CMS回收就会失败,虚拟机将启动老年代串行回收器进行垃圾回收,这回导致应用程序中断,直到垃圾回收完成后才会正常工作,这个过程GC的停顿时间可能较长,所以该值需要根据实际情况设置。
-XX:+UseCMSCompactAtFullCollection
设置cms在垃圾收集完成后进行一次内存碎片整理
-XX:CMSFullGCsBeforeCompaction
设定进行多少次cms回收后,进行一次内存压缩。
1、当今收集器技术发展的最前沿成果之一
2、G1是一款面向服务端应用的垃圾收集器。
3、优点:
4、G1收集器的运作大致可划分为以下几个步骤:
-XX:+UserG1Gc
应用G1收集器
-XX:MaxGCPauseMillis
指定最大停顿时间
-XX:ParallelGCThreads
设置并行回收的线程数量
在众多的垃圾回收器中,没有最好的,只有最适合应用的回收器,根据应用软件的特性以及硬件平台的特点,选择不同的垃圾回收器,才能有效的提高系统性能。
新生代 GC (Minor GC) :发生在新生代的垃圾收集动作。Minor GC 非常频繁,回收速度比较快。
老年代 GC (Major GC/Full GC):发生在老年代的 GC, Major GC 一般比 Minor GC 慢 10 倍以上。
对象优先在 Eden 分配
大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起 Minor GC。
大对象直接进入老年代
长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生在 Survivor 区中每熬过一次 Minor GC,年龄加 1 岁,当年龄到一定程度(默认15岁),就会晋升到老年代中。
动态对象年龄判定
在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全的。如果不成立,则虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可能连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这个 Minor GC 是有风险的;如果小于,或 HandlePromotionFailure 设置不允许冒险,那这次也要改为进行一次 Full GC。
优秀的编程习惯
(1)避免在循环体中创建对象,即使该对象占用内存空间不大。
(2)尽量及时使对象符合垃圾回收标准。
(3)不要采用过深的继承层次。
(4)访问本地变量优于访问类中的变量。
所有的XX参数都以”-XX:”开始,但是随后的语法不同,取决于参数的类型。
对于布尔类型的参数,我们有”+”或”-“,然后才设置JVM选项的实际名称。例如,-XX:+
用于激活
选项,而-XX:-
用于注销选项。
对于需要非布尔值的参数,如string或者integer,我们先写参数的名称,后面加上”=”,最后赋值。例如, -XX:
给
赋值
。
-XX:+PrintGC
虚拟机启动后,只要遇到GC就会打印日志
-XX:+PrintGCDetails
查看详细信息,包括各个区的情况
-XX:+PrintGCTimeStamps
可以将时间和日期也加到GC日志中。表示自JVM启动至今的时间戳
-XX:+PrintGCApplicationStoppedTime
打印垃圾回收期间程序暂停的时间.可与上面混合使用
-XX:+PrintGCDateStamps
每一行就添加上了绝对的日期和时间。(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC
在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log
日志文件的输出路径
-XX:+PrintTLAB
查看TLAB空间的使用情况
-Xms20M
,-XX:InitialHeapSize简写 表示设置堆容量的最小值为20M,必须以M为单位
-Xmx20M
-XX:MaxHeapSize简写,表示设置堆容量的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免堆自动扩展,减少程序运行时的垃圾回收次数,从而提供性能。大的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高
所有JVM关于初始\最大堆内存大小的输出都是使用它们的完整名称:“InitialHeapSize”和“InitialHeapSize”。所以当你查询一个正在运行的JVM的堆内存大小时,如使用-XX:+PrintCommandLineFlags参数或者通过JMX查询,应该寻找“InitialHeapSize”和“InitialHeapSize”标志而不是“Xms”和“Xmx”
稳定的堆大小能减少gc次数,但是每次gc时间增加
震荡的堆大小能增加gc次数,但是每次gc时间减少-XX:MinHeapFreeRatio
最小空闲比例,当堆空间空闲内存小于这个比例,则扩展-XX:ManHeapFreeRatio
最大空闲比例,当堆空间空闲内存大于这个比例,则压缩
-Xms和-Xmx相等时,上面的参数失效
-XX:+HeapDumpOnOutOfMemoryError
使得JVM在产生内存溢出时自动生成堆内存快照
-XX:HeapDumpPath=
改变默认的堆内存快照生成路径,可以是相对或者绝对路径
-XX:OnOutOfMemoryError
当内存发生溢出时 执行一串指令-XX:OnOutOfMemoryError ="sh ~/cleanup.sh"
-XX:PermSize
设置永久代的初始大小
-XX:MaxPermSize
设置永久代的最大大小
-Xmn
或 -XX:NewSize
设置新生代的初始大小
新生代只是堆的一部分 新生代越大老年代越小,一般不允许新生代比老生代还大。考虑到GC最坏的情况 新生代全部复制到老生代会产生OOM错误,这个参数对系统性能以及GC行为有很大影响,新生代大小一般会设置整个堆空间的1/3到1/4左右
-XX:NewRatio
设置新生代和老生代的相对大小。优点是新生代大小会随着整个堆大小动态扩展
不同的堆分布情况对系统执行都会产生不一样的影响,实际配置的基本策略就是尽可能将对象预留在新生代,减少老年代的GC次数。
-XX:SurvivorRatio
设置新生代中eden空间和from/to空间的比例,eden/to=eden/from
-XX:+PrintTenuringDistribution
指定JVM在每次新生代GC时 输出Survivor中对象的年龄分布
-XX:InitialTenuringThreshold
设置老年代阈值的初始值
-XX:MaxTenuringThreshold
设置新生代阈值的最大值(垃圾最大年龄),默认为15。如果设置为0的话,则新生代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则新生代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在新生代即被回收的概念。
另外,大对象(新生代eden区无法装入时,也会直接进入老年代)。
-XX:PretenureSizeThreshold
设置对象的大小超过指定的大小后,直接进入老年代,但是要注意TLAB区域优先分配空间的原则
TLAB 全称Thread Local Allocation Buffer,即线程本地分配缓存,从名字上看是一个线程专用的内存分配区域,是为了加速对象分配而生的。每一个线程都会产生一个TLAB,该线程独享的工作区域,java虚拟机使用这种TLAB区来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。
-XX:+NeverTenure
对象永远不会晋升到老年代(当不需要老生代的时候可以这样设置)
-XX:+AlwaysTenure
表示没有Survivor区 对象会直接被移动到老年代中
-XX:+UserTLAB
使用TLAB
-XX:+TLABSize
设置TLAB大小
-XX:+TLABRefllWasteFraction
设置维护进入TLAB空间的单个对象大小,他是一个比例值,默认为64,即如果对象大于整个空间的1/64,则在堆中创建对象。
-XX:+PrintTLAB
查看TLAB信息
-XX:+ResizeTLAB
自调整TLABRefllWasteFraction阀值
-XX:TLABWasteTargetPercent
TLAB占eden区的百分比
-Xss5m
指定线程的最大栈空间,整个参数也直接决定了函数可调用的最大深度
-XX:PermSize=10M
表示JVM初始分配的永久代的容量,必须以M为单位
-XX:MaxPermSize=10M
表示JVM允许分配的永久代的最大容量,必须以M为单位,大部分情况下这个参数默认为64M
方法区,又称永久区,默认情况下-XX:PermSize=64M,如果系统运行时生产大量的类,就需要设置一个相对合适的方法区,以免出现永久区内存溢出的问题
-XX:MaxDirectMemorySize=128m
直接内存最大容量,M单位,默认为64M
如果不设置默认值为最大堆空间,即-Xmx。直接内存使用达到上限时,就会触发垃圾回收,如果不能有效的释放空间,也会引起系统的OOM
-XX:+UseFastAccessorMethods
原始类型的快速优化
-XX:+DisableExplicitGC
关闭System.gc()
-XX:+AggressiveOpts
加快编译
-XX:+UseBiasedLocking
锁机制的性能改善
-Xnoclassgc
禁用垃圾回收
目前Java虚拟机支持Client和Server两种运行模式,使用参数-client可以指定使用Client模式,使用-server即使用Server模式,可以直接在命令行查看当前计算机系统自动选择的运行模式。java -version
二者区别:Client模式相对Server启动较快,如果不追究系统的长时间使用性能仅仅是测试,可以使用Client模式,而Server模式则启动较慢,原因是会对其进行复杂的系统性能信息收集和使用更复杂的算法对程序进行优化,一般我们的生产环境都会使用Server模式,长期运行其性能要远远快于Client模式
Server:默认为堆提供了一个更大的空间和并行的垃圾收集器 并且在运行时可以更大程度的优化代码
Client:客户端虚拟机有较小的默认堆内存 可以缩短JVM启动的时间和占用更少的内存 客户端的JVM只有在32位操作系统中才有
JDK1.7之后就不区分了,全部按Server模式工作
收集器设置:-XX:+UseSerialGC
新生代串行(Serial),老年代串行(Serial Old)
-XX:+UseParNewGC
新生代并行(ParNew),老年代串行(Serial Old)
-XX:+UseConcMarkSweepGC
新生代并行(ParNew),老年代串行(CMS),备份(Serial Old)
-XX:+UseParallelGC
新生代并行吞吐(Parallel Scavenge),老年代串行(Serial Old)
-XX:+UseParalledlOldGC
新生代并行吞吐(Parallel Scavenge),老年代并行吞吐(Parallel Old)
并行收集器设置-XX:ParallelGCThreads=n
设置并行收集器收集时使用的CPU数。并行收集线程数。一般最好和计算机的CPU相当
-XX:MaxGCPauseMillis=n
设置并行收集最大暂停时间
-XX:GCTimeRatio=n
设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
-XX:+CMSIncrementalMode
设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n
设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
工具:JDK自带的jvisualvm.exe、jconsole.exe
参考:
《深入理解Java虚拟机》《Hotspot实战》
链接:Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收 - CSDN博客
链接:JVM 自动内存管理:对象判定和回收算法-极客学院
链接:Java 技术,IBM 风格: 垃圾收集策略,第 1 部分
链接:JVM 垃圾回收器工作原理及使用实例介绍
球友分享