JVM 调优总结
一、堆与栈
1、堆是所有对象共享的,存储对象信息;
每一个线程都有一个独立的线程栈,栈存储的是与当前线程相关的信息,包括局部变量、程序运行状态、方法返回值等。
2、堆中存储的是对象,栈中存储的是基本类型和堆中对象的引用。
3、栈的大小配置 -Xss
4、在运行栈中,基本类型和引用的处理是一样的,都是传值。
5、可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一棵树(非叶子节点),而基本类型则是叶子节点。
二、Java对象的大小
1、一个空的Object对象:
Object obj = new Object();
其所占的空间为: 8+4 =12 bytes 。其中8 bytes 是Java堆中对象的所需空间,4 bytes是栈中保存引用的所需空间
2、以下类
class NewObject
{
int count;
boolean flag;
Object obj;
}
其的对象 NewObject newObj = new NewObject();
newObj 所占空间 8 + 4 +1 + 4 = 17 bytes.
其中8 bytes 为 newObj 空对象大小,4 bytes为 int 大小,1 byte为 boolean 大小, 4 bytes为 obj引用大小。
又因为Java对象内存分配以8的整数倍来分,故而对象的大小为 24 bytes。
3、基本类型包装类的大小至少是16bytes。
三、强引用、软引用、弱引用
强引用:回收时候严格判断
软引用:内存紧张则回收,富余则不回收
弱引用:每次GC必定回收,生命周期只存在于一个垃圾回收周期。
四、GC 基本策略
1、引用计算:无法处理循环引用
2、标记-清除:从root节点开始标记所有引用对象,遍历堆,清除未标记对象。缺点是需要暂停整个应用,产生内存碎片。
3、复制:将内存分为两块,每次只使用其中一块。回收时候复制到另一块。
4、标记整理:结合 “标记-清除” 和 “复制”, 不过不是复制到另一块内存,而是将对象放紧凑。
五、GC 分区策略
1、增量收集:实时垃圾回收。
2、分代收集:把对象分为年轻代、年老代、持久代,对于不同生命周期对象采用不同算法。
六、GC 线程策略
1、串行收集:单线程处理,无法使用多处理器的优势。需要暂停整个运行环境。
优点:简单,效率高。
缺点:只适合小型应用,数据量比较小。
配置:-XX:+UseSerialGC
2、并行收集:多线程处理,速度快,效率高。需要暂停整个运行环境。
优点:吞吐量大,适用于数值计算、后台处理。
缺点:响应时间长。
配置:XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=<N> -XX:MaxGCPauseMillis=<N> -XX:GCTimeRatio=<N>
3、并发收集:GC的同时不需要暂停整个运行环境。
优点:响应时间快,适用于Web服务器等。
缺点:吞吐量没那么大。
配置:-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=<N>
七、分代回收
1、年轻代(Yong):一个Eden区,两个Survivor区。Eden满,放入一个Survivor。Survivor放入另一个Survivor。另一个Survivor满,放入年老代。
2、年老代(Tenured):年轻代经历N次GC后,对象放到年老区。放的是生命周期较长的数据。
3、持久代(Permanent):Java 类的 Class 信息,与GC关系不大。
Minor GC:只要Eden满即触发,速度很快。
Full GC:整个堆回收,速度慢。在年老代或者持久代满的时候调用。
八、典型配置
java -Xmx3550m -Xms3550m -Xmn2g –Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-Xmx3550m: 最大可用内存
-Xms3550m:初始内存。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:年轻代大小
-Xss128k:每个线程的栈大小
-XX:NewRatio=4: 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)
-XX:SurvivorRatio=4:年轻代中Eden区与Survivor区的大小比值
-XX:MaxPermSize=16m: 持久代大小
-XX:MaxTenuringThreshold=0: 垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代
-XX:+UseParallelGC:选择并行收集器
-XX:ParallelGCThreads=20: 并行收集器的线程数
-XX:+UseParallelOldGC: 年老代垃圾收集方式为并行收集
-XX:MaxGCPauseMillis=100:每次年轻代垃圾回收的最长时间
-XX:+UseAdaptiveSizePolicy:自动选择年轻代区大小和相应的Survivor区比例
-XX:+UseConcMarkSweepGC:设置年老代为并发收集
-XX:+UseParNewGC: 设置年轻代为并行收集
-XX:CMSFullGCsBeforeCompaction=5:运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩
九、调优总结
1、吞吐量优先:
年轻代大、并行垃圾收集、年老代小。
尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
2、响应时间优先:
年轻代大、并发垃圾回收、年老代适中。
年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。
十、年老代碎片问题:
解决:
1、-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
2、-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
十一、常见JVM 异常
1、java.lang.OutOfMemoryError: Java heap space
原因:典型的内存泄露,所有堆空间都被无法回收的垃圾对象占满。
解决:找到泄露点,一般是集合对象引用。
2、java.lang.OutOfMemoryError: PermGen space
原因:持久代占满,无法为新的class分配存储空间而引发的异常。主要原因是大量动态反射生成的类不断被加载。不同的classloader 即便使用了相同的类,但是都会对其进行加载,相当于有一个class会被N个classloader加载N次。
解决:-XX:MaxPermSize=16m
3、java.lang.StackOverflowError
原因:递归没有返回,或者循环调用。
解决:修正代码
4、Fatal: Stack size too small
原因:线程空间大小被限制
解决:增大线程栈,-Xss2m。也可能是代码内存泄露。
5、java.lang.OutOfMemoryError: unable to create new native thread
原因:操作系统没有足够资源产生这个线程
解决:配置系统,如ulimit。减小线程栈大小,-Xss。