java实战--GC终极总结

GC简介

java的最大好处是自动垃圾回收,这样就无需我们手动的释放对象空间了,但是也产生了相应的负效果,gc是需要时间和资源的,不好的gc会严重影响系统的系能,因此良好的gc是JVM的高性能的保证JVM堆分为新生代,旧生代和年老代,新生代可用的gc方式有:串行gc(Serial Copying),并行回收gc(Parellel Scavenge),并行gc(ParNew),旧生代和年老代可用的gc方式有串行gc(Serial MSC),并行gc(Parallel MSC),并发gc(CMS)。

GC需要了解地方

1.哪些内存要回收

2.什么时候回收

3.怎么回收

哪些内存要回收

java内存模型中分为五大区域已经有所了解。我们知道程序计数器虚拟机栈本地方法栈,由线程而生,随线程而灭,其中栈中的栈帧随着方法的进入顺序的执行的入栈和出栈的操作,一个栈帧需要分配多少内存取决于具体的虚拟机实现并且在编译期间即确定下来【忽略JIT编译器做的优化,基本当成编译期间可知】,当方法或线程执行完毕后,内存就随着回收,因此无需关心。

Java堆方法区则不一样。GC垃圾回收主要集中在堆和方法区,在程序运行期间,这部分内存的分配和使用都是动态的。方法区存放着类加载信息,但是一个接口中多个实现类需要的内存可能不太一样,一个方法中多个分支需要的内存也可能不一样【只有在运行期间才可知道这个方法创建了哪些对象没需要多少内存】,这部分内存的分配和回收都是动态的,gc关注的也正是这部分的内存。

Java堆是GC回收的“重点区域”,堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。 堆中基本存放着所有对象实例,gc进行回收前,第一件事就是确认哪些对象存活,哪些死去[即不可能再被引用]

堆的回收区域

为了高效的回收,jvm将堆分为三个区域
1.新生代(Young Generation)NewSize和MaxNewSize分别可以控制年轻代的初始大小和最大的大小,1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1
2.老年代(Old Generation)
3.永久代(Permanent Generation)【1.8以后采用元空间,就不在堆中了】

堆大小 = 年轻代 + 老年代
年轻代 = eden space (新生代) + from survivor + to survivor堆大小 (两个幸存者区域)
年轻代 = eden space (新生代) + from survivor + to survivor,其中ServivorTo:保留了一次MinorGC过程中的幸存者。
ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者,MinorGC的过程:MinorGC采用复制算法。在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

年轻代用来存放新近创建的对象,尺寸随堆大小的增大和减小而相应的变化,默认值是保持为堆大小的1/15,可以通过 -Xmn 参数设置年轻代为固定大小,也可以通过 -XX:NewRatio 来设置年轻代与老年代的大小比例,年轻代的特点是对象更新速度快,在短时间内产生大量的“死亡对象”。
 

GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )


         新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。 年轻代的特点是产生大量的死亡对象,并且要是产生连续可用的空间, 为了提高内存的利用率所以采用复制回收算法进行垃圾回收.对年轻代的垃圾回收称作初级回收 (minor gc)
        当一个对象被判定为 "死亡" 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。 当对象在 Eden 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳,则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域中,然后清理所使用过的 Eden 以及 Survivor 区域,并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。 


注意: 但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
老年代:

Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法(这种算法可能会产生大量的内存碎片),或者标记整理算法(解决内存碎片问题)。现实的生活中,老年代的人通常会比新生代的人 "早死"。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 "死掉" 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 
另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作,MajorGC采用标记—清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。 当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常

判断对象是否存活算法

1.引用计数算法
                早期判断对象是否存活大多都是以这种算法,这种算法判断很简单,简单来说就是给对象添加一个引用计数器,每当对象被引用一次就加1,引用失效时就减1。当为0的时候就判断对象不会再被引用。
优点:实现简单效率高,被广泛使用与如python何游戏脚本语言上。
缺点:难以解决循环引用的问题,就是假如两个对象互相引用已经不会再被其它其它引用,导致一直不会为0就无法进行回收。

2.可达性分析算法
             目前java采用的是可达性分析算法判断对象是否存活。这个算法有效解决了循环利用的弊端。
它的基本思路是通过一个称为“GC Roots”的对象为起始点,搜索所经过的路径称为引用链,当一个对象到GC Roots没有任何引用跟它连接则证明对象是不可用的。

可作为GC Roots的对象有四种

①虚拟机栈(栈桢中的本地变量表)中的引用的对象,就是平时所指的java对象,存放在堆中。
②方法区中的类静态属性引用的对象,一般指被static修饰引用的对象,加载类的时候就加载到内存中。
③方法区中的常量引用的对象,
④本地方法栈中JNI(native方法)引用的对象

即使可达性算法中不可达的对象,也不是一定要马上被回收,还有可能被抢救一下。网上例子很多,基本上和深入理解JVM一书讲的一样对象的生存还是死亡

要真正宣告对象死亡需经过两个过程。
1.可达性分析后没有发现引用链
2.查看对象是否有finalize方法,如果有重写且在方法内完成自救[比如再建立引用],还是可以抢救一下,注意这边一个类的finalize只执行一次,这就会出现一样的代码第一次自救成功第二次失败的情况。[如果类重写finalize且还没调用过,会将这个对象放到一个叫做F-Queue的序列里,这边finalize不承诺一定会执行,这么做是因为如果里面死循环的话可能会时F-Queue队列处于等待,严重会导致内存崩溃,这是我们不希望看到的。]

java对象之间四种引用

强引用


只要引用存,垃圾回收器永远不会回收
Object obj = new Object();
//可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。


软引用


非必须引用,内存溢出之前进行回收,可以通过以下代码实现

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;

sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;


软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。


弱引用


第二次垃圾回收时回收,可以通过如下代码实现

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;

wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。


虚引用


垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;

pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。

垃圾收集算法

jvm中,可达性分析算法帮我们解决了哪些对象可以回收的问题,垃圾收集算法则关心怎么回收。

三大垃圾收集算法

1.标记/清除算法【最基础】
2.复制算法
3.标记/整理算法
jvm采用`分代收集算法`对不同区域采用不同的回收算法。

参考GC算法深度解析

新生代采用复制算法

新生代中因为对象都是"朝生夕死的",【深入理解JVM虚拟机上说98%的对象,不知道是不是这么多,总之就是存活率很低】,适用于复制算法【复制算法比较适合用于存活率低的内存区域】。它优化了标记/清除算法的效率和内存碎片问题,且JVM不以5:5分配内存【由于存活率低,不需要复制保留那么大的区域造成空间上的浪费,因此不需要按1:1【原有区域:保留空间】划分内存区域,而是将内存分为一块Eden空间和From Survivor、To Survivor【保留空间】,三者默认比例为8:1:1,优先使用Eden区,若Eden区满,则将对象复制到第二块内存区上。但是不能保证每次回收都只有不多于10%的对象存货,所以Survivor区不够的话,则会依赖老年代年存进行分配】。

GC开始时,对象只会存于Eden和From Survivor区域,To Survivor【保留空间】为空。

GC进行时,Eden区所有存活的对象都被复制到To Survivor区,而From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阈值(默认15是因为对象头中年龄战4bit,新生代每熬过一次垃圾回收,年龄+1),则移到老年代,没有达到则复制到To Survivor。

老年代采用标记/清除算法标记/整理算法

由于老年代存活率高,没有额外空间给他做担保,必须使用这两种算法。

枚举根节点算法

GC Roots 被虚拟机用来判断对象是否存活

可作为GC Roos的节点主要是在一些全局引用【如常量或静态属性】、执行上下文【如栈帧中本地变量表】中。那么如何在这么多全局变量和本地变量表找到【枚举】根节点将是个问题。

可达性分析算法需考虑

1.如果方法区几百兆,一个个检查里面的引用,将耗费大量资源。

2.在分析时,需保证这个对象引用关系不再变化,否则结果将不准确。【因此GC进行时需停掉其它所有java执行线程(Sun把这种行为称为‘Stop the World’),即使是号称几乎不会停顿的CMS收集器,枚举根节点时也需停掉线程】

解决办法:实际上当系统停下来后JVM不需要一个个检查引用,而是通过OopMap数据结构【HotSpot的叫法】来标记对象引用。

虚拟机先得知哪些地方存放对象的引用,在类加载完时。HotSpot把对象内什么偏移量什么类型的数据算出来,在jit编译过程中,也会在特定位置记录下栈和寄存器哪些位置是引用,这样GC在扫描时就可以知道这些信息。【目前主流JVM使用准确式GC】

OopMap可以帮助HotSpot快速且准确完成GC Roots枚举以及确定相关信息。但是也存在一个问题,可能导致引用关系变化。

这个时候有个safepoint(安全点)的概念。

HotSpot中GC不是在任意位置都可以进入,而只能在safepoint处进入。 GC时对一个Java线程来说,它要么处在safepoint,要么不在safepoint。

safepoint不能太少,否则GC等待的时间会很久

safepoint不能太多,否则将增加运行GC的负担

安全点主要存放的位置

1:循环的末尾 
2:方法临返回前/调用方法的call指令后 
3:可能抛异常的位置

GC回收器解读

目前主流的HotSpot VM支持多种虚拟机,这些虚拟机也体现了GC的发展历程,从单线程GC(由于早起机器单核处理)到多线程GC,分代GC到G1 GC。

本文主要从以下几个方面介绍GC收集器:

  • 各种GC的特点
  • GC匹配和参数使用
  • GC日志格式
  • 常用的GC参数总结

各种GC的特点

HotSpot中采用分代GC,从早期的单线程串行Garbage Collector到后面的多线程并行Garbage Collecot,衍生出了很多款Collector。

其中负责收集年轻代:

  1. Serial:单线程串行收集器,使用复制算法回收年轻代
  2. ParNew:多线程并行收集器,使用复制算法回收年轻代
  3. Parallel Scavenge:多线程并行收集器,使用复制算法回收年轻代,注重系统运行的吞吐量

其中负责收集老年代:

  1. Serial Old:类似Serial,单线程串行收集器,使用标记整理算法回收老年代
  2. CMS:并发标记整理的收集器,使用标记清除算法回收老年代,主要是为了减少停顿的时间,降低延迟而生
  3. Parallel Old:类似ParNew和Parallel Scavenge,使用标记整理算法回收老年代

低延迟方案应用趋势:CMS -> G1 -> ZGC

回收方式的选择

jvm有client和server两种模式,这两种模式的gc默认方式是不同的:

  1. client模式下,新生代选择的是串行gc,旧生代选择的是串行gc
  2. server模式下,新生代选择的是并行回收gc,旧生代选择的是并行gc
  3. 一般来说我们系统应用选择有两种方式:吞吐量优先和暂停时间优先,对于吞吐量优先的采用server默认的并行gc方式,对于暂停时间优先的选用并发gc(CMS)方式。

GC的选择
官方推荐,需要根据应用的实际情况进行选择。在选择之前必须要对应用的堆大小、收集频率进行估算。

使用SerialGC的场景:
1、如果应用的堆大小在100MB以内。
2、如果应用在一个单核单线程的服务器上面,并且对应用暂停的时间无需求。
使用ParallelGC的场景:
如果需要应用在高峰期有较好的性能,但是对应用停顿时间无高要求(比如:停顿1s甚至更长)。
使用G1、CMS场景:
1、对应用的延迟有很高的要求。
2、如果内存大于6G请使用G1。

什么时候会发生 Full GC ?

full gc是对新生代,旧生代,以及持久代的统一回收,由于是对整个空间的回收,因此比较慢,系统中应当尽量减少full gc的次数。

  1. Tenured空间不足:java.lang.OutOfMemoryError:Javaheapspace
  2. Perm空间不足:java.lang.OutOfMemoryError:PermGenspace
  3. CMS GC时出现 promotion failed 和 concurrent mode failure
  4. Tenured剩余空间小于MinorGC晋升的平均大小(悲观策略)
  5. 主动触发FullGC:System.gc(),jmap-histo:live[pid]

如何查看运行程序的GC情况

jstat命令命令格式:

jstat [Options] vmid [interval] [count]

参数说明:

Options,选项,我们一般使用 -gcutil 查看gc情况
vmid,VM的进程号,即当前运行的java进程号
interval,间隔时间,单位为秒或者毫秒
count,打印次数,如果缺省则打印无数次

示例

通常运行命令如下:   jstat -gc 30996 3000

即:每3秒一次显示进程号为30996的java进程的GC情况或使用命令:jstat -gcutil 30996 3000

结果说明

显示内容说明如下(部分结果是通过其他其他参数显示的,暂不说明):

  1.          S0C:年轻代中第一个survivor(幸存区)的容量 (字节) 
  2.          S1C:年轻代中第二个survivor(幸存区)的容量 (字节) 
  3.          S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节) 
  4.          S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节) 
  5.          EC:年轻代中Eden(伊甸园)的容量 (字节) 
  6.          EU:年轻代中Eden(伊甸园)目前已使用空间 (字节) 
  7.          OC:Old代的容量 (字节) 
  8.          OU:Old代目前已使用空间 (字节) 
  9.          PC:Perm(持久代)的容量 (字节) 
  10.          PU:Perm(持久代)目前已使用空间 (字节) 
  11.          YGC:从应用程序启动到采样时年轻代中gc次数 
  12.          YGCT:从应用程序启动到采样时年轻代中gc所用时间(s) 
  13.          FGC:从应用程序启动到采样时old代(全gc)gc次数 
  14.          FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s) 
  15.          GCT:从应用程序启动到采样时gc用的总时间(s) 
  16.          NGCMN:年轻代(young)中初始化(最小)的大小 (字节) 
  17.          NGCMX:年轻代(young)的最大容量 (字节) 
  18.          NGC:年轻代(young)中当前的容量 (字节) 
  19.          OGCMN:old代中初始化(最小)的大小 (字节) 
  20.          OGCMX:old代的最大容量 (字节) 
  21.          OGC:old代当前新生成的容量 (字节) 
  22.          PGCMN:perm代中初始化(最小)的大小 (字节) 
  23.          PGCMX:perm代的最大容量 (字节)   
  24.          PGC:perm代当前新生成的容量 (字节) 
  25.          S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 
  26.          S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 
  27.          E:年轻代中Eden(伊甸园)已使用的占当前容量百分比 
  28.          O:old代已使用的占当前容量百分比 
  29.          P:perm代已使用的占当前容量百分比 
  30.          S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节) 
  31.          S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节) 
  32.          ECMX:年轻代中Eden(伊甸园)的最大容量 (字节) 
  33.          DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满) 
  34.          TT: 持有次数限制 
  35.          MTT : 最大持有次数限制 

理解 GC 并发收集的思路

• 并发GC根本上要跟应用玩追赶游戏:应用一边在分配,GC一边在收集,如果GC收集的速度能跟得上 应用分配的速度,那就一切都很完美;一旦GC开始跟不上了,垃圾就会渐渐堆积起来,最终到可用空 间彻底耗尽的时候,应用的分配请求就只能暂时等一等了,等GC追赶上来。

1.Serial收集器

Serial收集器是一款非常古老的收集器,它使用单线程串行方式回收年轻代,会产生STW。

Note:
STW即Stop The World,即停止所有用户线程,只有GC线程在运行。

每次进行GC时,首先停顿所有的用户线程,然后只有一个GC线程回收年轻代中的死亡对象。在Java Client模式中,默认使用Serial,因为Client模式主要针对桌面应用,一般内存较小,在百M范围内,使用单线程收集Serial效率非常高,可以带来很少时间的停顿,用户体检非常好。

2.ParNew收集器

在早期,只有单线程收集器时,年轻代别无选择。后续又演变成多线程GC年轻代,便衍生出ParNew这款并行收集器,它的并行实现主要是在GC期间使用多线程回收年轻代。

这款并行收集器在GC期间,也需要STW。一般多数用于Server端的年轻代GC。

3.Parallel Scavenge收集器

顾名思义,这个款年轻代收集器也是并行收集器,和ParNew的功能差不多,同样适用复制算法。但是它更注重系统运行的吞吐量。这里说的吞吐量,指的是CPU用于运行应用程序的时间和CPU总时间的占比,吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间)。但是它的来源比较奇葩,没有遵循GC框架,导致和CMS不能兼容。关于这点可以参考:

ParNew 和 PSYoungGen 和 DefNew 是一个东西么?

4.Serial Old收集器

该收集器和Serial收集器的功能一样,都是单线程串行收集器,GC期间也会STW。但是它用于收集老年代且使用了标记整理算法,这两点它和Serial收集器不一样。主要也是应用在Client模式下的桌面应用中。

5.Parallel Old收集器

Parallel Old和ParNew和Parallel Scavenge类似,是一款老年代的多线程并行收集器。一般只配合Parallel Scavenge使用。

6.CMS收集器

CMS(Concurrent Mark Sweep 并发标记清理)收集器是日常应用中最常被使用的收集器。它主要是为了减少停顿的时间,降低延迟而生,多应用对实时性要求比较的应用场景,如:互联网应用。

它主要分为四个过程:

  1. 初始标记:这阶段将标记不可达对象,标记阶段将暂停所有用户线程,造成STW。但是这阶段只是标记出GC Roots,停顿时间相对较短。
  2. 并发标记:这阶段GC线程将会和用户线程同时运行,将从初始阶段标记出的GC Roots出发标记老年代所有对象
  3. 重新标记:这阶段将暂停所用用户线程,造成STW。但是同样相对较短,主要是为了重新标记出在并发阶段发生引用变化的对象,因为并发标记阶段是和用户线程并发运行,可能会造成对象的引用关系发生变化。
  4. 并发清除:这是最后一个阶段,也是和用户线程同时运行的。将并发的清理掉被标记的死亡对象。

其中初始标记和重新标记仍然会STW暂停用户线程,但是这两个过程的停顿时间相对于并发标记和并发清除而言相对较短,而并发标记和并发清除阶段GC线程则可以和用户线程并发运行。

由于CMS收集器同样使用标记清除算法,所以存在内存碎片问题,从而可能造成大对象无法分配发生提前GC。所以CMS收集器又提供了参数控制其进行内存碎片整理,默认是开启状态,这个过程是非常长的。

GC匹配和参数使用

从以上内容介绍,可以看出分代GC分为很多种,随着演化过程,每种都有各自的应用场景。从其收集特点上可以分为三类:

  1. 单线程串行收集
  2. 多线程并发串行收集
  3. 多阶段并行收集

虽然这些分代收集器种类繁多,但是他们之间有相互匹配,并非任意使用。配对的使用情况以及参数见下表:

youngold参数
SerialSerial old-XX:+UseSerialGC
ParNewSerial old-XX:+UseParNewGC
Parallel ScavengeSerial old-XX:+UseParallelGC
Parallel ScavengeParallel Old-XX:+UseParallelOldGC
ParNewCMS + Serial Old-XX:+UseConcMarkSweepGC
SerialCMS-XX:+UseConcMarkSweepGC -XX:-UseParNewGC

注意:利用-XX:+UseG1GC -XX:+UseStringDeduplication来减少重复字符串导致的内存浪费

堆(Heap)和非堆(Non-heap)内存
    按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给 自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法 的代码都在非堆内存中。
堆内存分配
    JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
非堆内存分配
    JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
JVM内存限制(最大值)
    首先JVM内存限制于实际的最大物理内存(废话!呵呵),假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然 可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统 下为2G-3G),而64bit以上的处理器就不会有限制了。

GC日志

开启GC日志

 -XX:+PrintGCDetails -Xloggc:  xxxx  
// 开启GC日志,xxxx表示GC日志输出的位置
// 虽然输出日志会有性能开销,相比日后观察JVM定位问题这点开销还是值得的。

GC日志参数详解

  • -verbose.gc开关可显示GC的操作内容。打开它,可以显示最忙和最空闲收集行为发生的时间、收集前后的内存大小、收集需要的时间等。
  • 打开-xx:+printGCdetails开关,可以详细了解GC中的变化。
  • 打开-XX:+PrintGCTimeStamps开关,可以了解这些垃圾收集发生的时间,自JVM启动以后以秒计量。
  • 最后,通过-xx:+PrintHeapAtGC开关了解堆的更详细的信息。
  • 为了了解新域的情况,可以通过-XX:+PrintTenuringDistribution开关了解获得使用期的对象权。
  • -Xloggc:$CATALINA_BASE/logs/gc.log gc 配置gc日志产生的路径
  • -XX:+PrintGCApplicationStoppedTime 输出GC造成应用暂停的时间
  • -XX:+PrintGCDateStamps GC发生的时间信息
-Xmx${HEAPSIZE}m -Xms${HEAPSIZE}m -XX:+UseG1GC -XX:MaxGCPauseMillis=80 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:${LOGS_DIR}

常用JVM参数

  1. -Xmn    新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% 
  2. -Xss    JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。
  3. -XX:NewRatio    新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3
  4. -XX:SurvivorRatio    新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 
  5. Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
  6. Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。
  7. Xss 是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。
  8. 以上三个参数的设置都是默认以Byte为单位的,也可以在数字后面添加[k/K]或者[m/M]来表示KB或者MB。而且,超过机器本身的内存大小也是不可以的,否则就等着机器变慢而不是程序变慢了。
  • -Xms 为jvm启动时分配的内存,比如-Xms200m,表示分配200M,一般与-Xmx设置一样大
  • -Xmx 为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存,
  • -Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M
Total Memory-Xms-Xmx-XssSpare MemoryJDKThread Count
1024M256M256M256K768M1.43072
1024M256M256M256K768M1.5768

此外还有一个持久代: 用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过 -XX:MaxPermSize=<N> 进行设置。

并行收集器相关参数

-XX:+UseParallelGCFull GC采用parallel MSC
(此项待验证)
 

选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集.(此项待验证)

-XX:+UseParNewGC设置年轻代为并行收集 可与CMS收集同时使用
JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值
-XX:ParallelGCThreads并行收集器的线程数 此值最好配置与处理器数目相等 同样适用于CMS,默认与CPU核数相同
-XX:+UseParallelOldGC年老代垃圾收集方式为并行收集(Parallel Compacting) 这个是JAVA 6出现的参数选项
-XX:MaxGCPauseMillis每次年轻代垃圾回收的最长时间(最大暂停时间) 如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.
-XX:+UseAdaptiveSizePolicy自动选择年轻代区大小和相应的Survivor区比例 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开.
-XX:GCTimeRatio设置垃圾回收时间占程序运行时间的百分比 公式为1/(1+n)
-XX:+ScavengeBeforeFullGCFull GC前调用YGCtrueDo young generation GC prior to a full GC. (Introduced in 1.4.1.)

辅助信息

-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]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

-XX:+PrintGCTimeStamps   
-XX:+PrintGC:PrintGCTimeStamps  可与-XX:+PrintGC -XX:+PrintGCDetails混合使用
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationStoppedTime打印垃圾回收期间程序暂停的时间.可与上面混合使用 输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:+PrintGCApplicationConcurrentTime打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用 输出形式:Application time: 0.5291524 seconds
-XX:+PrintHeapAtGC打印GC前后的详细堆栈信息  
-Xloggc:filename把相关日志信息记录到文件以便分析.
与上面几个配合使用
  

-XX:+PrintClassHistogram

garbage collects before printing the histogram.  
-XX:+PrintTLAB查看TLAB空间的使用情况  
XX:+PrintTenuringDistribution查看每次minor GC后新的存活周期的阈值 

Desired survivor size 1048576 bytes, new threshold 7 (max 15)
new threshold 7即标识新的存活周期的阈值为7。

有的虚拟机并没有持久代,JAVA8 开始持久代也已经被彻底删除了,取代它的是另一个内存区域也被称为 元空间。、

示例

下面对如下的参数进行分析:

JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log 
-Djava.awt.headless=true 
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails 
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"
  • -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m

Xms,即为jvm启动时得JVM初始堆大小,Xmx为jvm的最大堆大小,xmn为新生代的大小,permsize为永久代的初始大小,MaxPermSize为永久代的最大空间。

  • -XX:SurvivorRatio=4

SurvivorRatio为新生代空间中的Eden区和救助空间Survivor区的大小比值,默认是32,也就是说Eden区是 Survivor区的32倍大小,要注意Survivo是有两个区的,因此Surivivor其实占整个young genertation的1/34。调小这个参数将增大survivor区,让对象尽量在survitor区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如65536)来做到。

  • -verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log

将虚拟机每次垃圾回收的信息写到日志文件中,文件名由file指定,文件格式是平文件,内容和-verbose:gc输出内容相同。

  • -Djava.awt.headless=true

Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。

  • -XX:+PrintGCTimeStamps -XX:+PrintGCDetails

设置gc日志的格式

  • -Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000

指定rmi调用时gc的时间间隔

  • -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15

采用并发gc方式,经过15次minor gc 后进入年老代

Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。

Xss 是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。

以上三个参数的设置都是默认以Byte为单位的,也可以在数字后面添加[k/K]或者[m/M]来表示KB或者MB。而且,超过机器本身的内存大小也是不可以的,否则就等着机器变慢而不是程序变慢了。
 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿华田512

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值