一、jvm堆大小的限制
1、操作系统的数据模型(32位或64位)限制
2、系统可用的虚拟内存限制
3、系统可用的物理内存限制
32位的操作系统,内存限制一般在1.5g到2g,64位内存一般没有限制
二、堆空间基本结构
jvm实例只存在一个堆内存,堆内存可以调整。堆内存包含三部分:
Young Gerneration Space 新生代
Old Gerneration Space 旧生代
Parmanent Space 永久代
jdk1.8之后永久代变为元空间
新生代又分为Eden区,survivor from区和to区,也称作s0区和s1区
三、垃圾回收的原理
将内存中不在引用的实例对象清除掉,释放内存空间。由于GC需要消耗一些资源和时间,GC会按照新生代,老年代方式进行回收,尽可能的减少暂停系统时间。
GC引起的应用暂停
对新生代对象进行的收集叫做minor GC
对老年代对象进行的收集叫做full GC
程序中主动调用System.gc()叫做full GC
jvm对象的引用类型使用不同的垃圾回收
强引用:可以直接访问到对象实例,如果存在强引用,什么时候都不能进行回收,就算jvm抛出oom,也不会回收,只有判断为被引用,才会回收,强引用可能引起内存泄漏
软引用:仅次于强引用,通过Java.lang.ref.SoftReference使用软引用,持有软引用的对象不容易被回收,可以根据堆内存的使用情况判断是否被回收。当堆的使用率接近临界值时,对弱引用进行回收,不过可以利用这点实现内存的高速缓存。
弱引用:弱引用在GC时,只要发现弱引用存在,不管堆内存是否充足,都会立刻回收。
虚引用:虚引用跟没有引用差不多,随时都可能被回收,
四、标记为垃圾
jvm内存区包含了五大部分:堆,方法区,虚拟机栈,本地方法栈,程序计数器,其中虚拟机栈,本地方法栈,程序计数器为线程私有部分,随着线程的开始而开始,随线程的结束而结束,这部分就不用考虑回收问题。而堆和方法区这部分是动态分配的,有好多不确定性,就需要对应的垃圾回收机制,垃圾回收的关注点也是堆和方法区的内存释放。
1、引用计数器
堆中每个对象实例都有一个引用计数,当一个对象被创建,就将这个对象的实例分配一个变量,变量值设置成1,当这个对象被引用时,计数器就+1,当这个实例引用超过生命周期之后计数器-1。任何计数器值为0的实例对象都可以被回收,当对象引用的其他对象的计数器都-1。引用计数器时垃圾收集早期的策略。
优点:程序不会被长时间打断的情况下,程序中交织执行,执行速度快。
缺点:无法检测出循环引用。
2、可达性分析
程序引用可以看作一个图,可以从GC-roots节点开始,寻找对应的引用节点,找到这个节点之后,继续寻找这个节点的引用节点,当所有节点寻找完毕后,剩余的节点就被认为时未引用节点,这些节点成为无用节点,也是回收的对象。
Java语言中,被认定为Gc-roots节点有哪些呢?
虚拟机栈中的引用对象,方法区中静态变量引用的对象,方法区中引用的对象,本地方法栈中native方法引用对象。
可达性分析是目前流行虚拟机的采用的算法策略。
五、垃圾收集算法
1、复制算法
复制算法将内存按容量分成大小相同的两份,每次使用其中一块,当这一块用完了之后,就将存活的对象复制到另一块,然后把这块内存中的所有数据全部清空。复制算法每次都是对整个半区进行操作,这样就减少了对标记对象的遍历,在清除使用区域不需要遍历,直接清空整个区域内存,而且存活对象复制到保留区顺序存储,这样减少了内存碎片的整理,只需要顺序存储就可以。
优点:复制算法简单高效,避免了标记清楚,碎片整理等问题
缺点:将一半的内存使用,一半的内存浪费,代价比较高
2、标记清除算法
标记清除算法包含两个阶段:首先先要标记出要回收的对象,然后标记完成后统一进行回收。
A.标记阶段
通过可达性分析,在对象header 里面打一个可达性标志,被标记为可达性对象
B.清除阶段
对堆内存里面进行遍历,如果发现某个对象没有被标记为可达性,就进行回收
在标记可达性分析的过程中,避免引用关系发生改变,为了保证可达性分析结果的准确性,避免引用关系发生改变,应用线程会暂时停止,等标记完成后应用线程恢复。
3、标记整理算法
标记整理算法和标记清除算法一直,通过可达性分析标记出不可回收对象,将不可回收对象整理并移动到一端,将端边线以外的进行直接清理。
优点:标记整理弥补了标记删除算法内存碎片问题消除和复制算法一半内存的浪费问题。
缺点:标记过程会引起stop the world,还有要整理不可回收对象的地址,效率上没有复制算法高,
4、分代回收算法
分代收集算法就将内存分为年轻代和老年代,根据不同代的特点不同选用不同的回收算法。
新生代:朝生夕灭,存活时间段,采用复制算法。
老年代:经过多次minor GC存活下来的对象,存活周期比较长,适合用标记清除或者标记整理算法
新生代对象发生回收,会有大量的对象被回收,只有少量的幸存对象,只要付出少量的对象复制的成本进行收集。
老年代对象存活周期比较长,如果使用复制算法,没有额外的存储空间进行分配担保,所以只能使用标记清除或者标记整理进行收集
六、jvm调优参数设置以及详解
1. 堆大小设置
-Xms:堆的初始大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRatio:设置新生代和老年代的大小比例,如为3,新生代是1,老年代为3
-XX:SurvivorRatio:设置新生代eden和survivor大小比例,注意Survivor是有两个区,from和to,如果设置为3,那么Survivor每个区占用新生代的1/5
-XX:MaxTenuringThresHold:设置转入老年代的年龄,如果是0,直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:设置永久代的最小大小和最大大小(JDK8之后取消了永久代)
-XX:MetaSpaceSize、-XX:MaxMetaspaceSize:设置元空间的最小值和最大值(JDK8及以上)
-Xss:设置线程内栈的大小,JDK5以前默认大小为256K,之后设置成了1M,这个应根据具体的应用进行设置大小,在内存一定的情况下,这个值设置的越小就可以创建更多的线程,但是也不是可以无限创建的,操作系统对线程的创建也是有一定限制的,这个可以根据经验设置。如果这个值设置的比较大的话,对应的栈数量就会减少,如果在多线程的情况下可能会出现内存溢出。
2、回收器设置
JVM提供了三种回收器,串行回收器,并行回收器,并发回收器
A、串行回收器
-XX:+UseSerialGC
B、并行回收器
-XX:+UseParallelGC
-XX:+UserParallelOldGC
-XX:+ParallelGCThreads=n:设置并行收集线程数
-XX:+MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:+GCTimeRatio=n:设置并行收集时间占程序运行时间的比例,公式(1/(1+n))
C、并发回收器
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC(JDK5以上就不用再配置了,系统会根据情况自行设置)
-XX:+ParallelThreads=n:年轻代并发收集时,使用的线程数
3、垃圾回收统计设置
-XX:+PrintGC打印GC日志
-XX:+PrintGcDetails打印详细日志
-XX:+PrintGcTimeStamps
-Xloggc:filename
七、经典配置
1.吞吐量优先配置(并行收集器)
Java -Xms3800m -Xmx3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
UseParallelGC使用并行收集器,对于新生代使用并发收集器,老年代默认使用串行收集器
ParallelGCThreads=20新生代并行收集总共有20个线程进行收集,这个值的设置最好根据cpu的核心数来设置
Java -Xms3800m -Xmx3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:+ParallelGCThreads=20 -XX:+UseParallelOldGC
UseParallelOldGC配置老年代使用并行回收器收集
java -Xms3800m -Xmx3800m -Xmn2g -XX:+UseParallelGC -XX:+MaxGCPauseMillis=100
-XX:+MaxGCPauseMillis=100配置年轻代每次垃圾回收使用的最长时间,如果没法满足这个时间,JVM会自动调节新生代的大小,来满足这个时间
java -Xms3800m -Xmx3800m -Xmn2g -XX:+UseParallelGC -XX:+MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy配置之后并行收集器会自动选择年轻代区大小和相应survivor比例值,以达到应用程序规定最低响应时间和收集频率等等
2、响应时间优先
java -Xms3800m -Xmx3800m -Xmn2g -Xss128k -XX:+ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParallelGC
UseConcMarkSweepGC配置了老年代并发收集
八、调优经验总结
1.年轻代大小选择
吞吐量高应用:尽量设置大点,因为对响应时间没有限制,垃圾回收可以并行收集。
响应时间的应用:设置大点,尽量接近系统最低响应时间限制。这样年轻代GC频率也会变小,也可以减少移动对象到老年代
2、老年代大小的选择
吞吐量高应用:吞吐量大应用,一般都有比较大的年轻代和相对小的老年代,存活时间比较短的对象进行回收,减少老年代当中的中期对象,只存储存活周期比较长的对象。
响应时间的应用:老年代使用并发收集器,大小的设置一般要考虑并发会话率和会话持续时间等参数设置。堆设置小了会增加收集频率和增加内存碎片以及应用暂停而是用标记清除的方式,如果堆设置大了,则需要较长的收集时间,最优的方式可以参考一下两个参数来设置:并发垃圾收集信息和以及并发收集次数