jvm虚拟机对内存管理主要体现在堆内存的管理上,我们可以在启动jvm的时候设置jvm对内存大小及调整策略。
1.jvm启动参数:
-Xms:jvm启动时初始堆大小。
-Xmx:jvm堆的最大值。
-Xss:线程栈大小。
-Dname=value:jvm全局属性设置。
jvm启动参数设置有很多,以上只是列举本人接触过的几个参数。
1)首先,-Xms是jvm启动时堆内存的初始大小,当堆内存不够用时,jvm调整堆大小到-Xmx设置的大小。一般resin这些服务器会把-Xms和-Xmx大小设置一样以避免不必要的内存调整。第一次对这个参数有印象是在刚到公司实习的第二天,公司使用的项目是用maven编译的,在maven配置文件settings.xml中有maven的资源库位置以及编译的各项配置。看完各种新手入门教程之后开始下载部门主工程代码,用maven编译一直通过不了,原因就在于jvm内存不够,当时maven中设置-Xms:256m,-Xmx:256m,显然256m是不够的,-Xmx设置为512m之后就可以编译过了。还有一个问题就是,这里把-Xms和-Xmx设置一样大是不合理的,虽然两个大小设置一样会避免编译主工程的时候jvm调整堆内存大小,但是maven编译其他比较小的项目的时候也要分配512m有些过于浪费,jvm占用过多的内存会让机器很卡的。
2)-Dname=value这个参数使用的更频繁,在resin的httpd.sh文件中配置这个参数可以在项目启动的时候将指定的域名映射到想要的ip上去,使得调试更为方便。其他作用的设置暂时还不了解。
2.jvm堆内存分布
刚刚毕业的时候,面试官问jvm内存知道么,我会脱口而出jvm内存分为堆和栈。直到第一次代码不小心造成了内存泄露,当时tail了一下resin的jvm.log发现gc和full gc的频率很高,正常情况下tail这个日志很少会有gc或者full gc。看着一条一条jvm日志,完全像是在看另外一个世界的东西,这时我才意识到jvm管理的内存只知道这些是不够的。
事实上jvm虚拟机管理的内存主要有:程序计数器,堆,方法区(永久代),虚拟机栈,本地方法栈。堆是jvm管理的最大的一块内存,主要的作用:存放几乎所有的类的实例对象。
首先,jvm对内存分为新生代(Young)和老年代(Old).新生代又可以划分为三块,这样jvm中就有新生代,老年大,永久代几片内存,这样分区的目的就是垃圾回收时使用分代算法。
新生代被划分为三个区域:
Eden:几乎所有新诞生的java对象存在这个区域。
From Survivor/To Survivor:经历过gc之后仍然存活且未进入到老年代区域的对象。
Young和Old大小比例是1:2,Young中Eden:From:To是8:1:1。这个比例由参数 -XX:SurvivorRatio=8 来决定的。
3.gc发生条件
首先,一般的对象产生都会在Eden中,较大的对象会直接进入老年代这个由参数 -XX:PretenureSizeThreshold 设置。在新生代中三个区域eden,from,to,一个时刻只会有两片内存被使用,首先eden肯定会被使用,from和to只有一片会被使用,主要是由于虚拟机采用的复制算法。
minor gc:为了避免在gc的时候产生内存碎片,jvm以牺牲空间的方式来做的,首先eden空间不足时会产生一次minor gc,垃圾回收器会在eden和一片使用的Survivor(假设是from)中进行清理,存活下来的对象会被复制到to中(假设to的大小足够装满),然后清空eden和from,保留下来的对象年龄加一。当年龄到达某一个设定值时会进入老年代,默认是15岁,由参数 -XX:MaxTenuringThreshold设置。还有一种情况是在Survivor区域相同年龄多有对象大于Survivor区域一半是所有该年龄及以上的都会被移动到老年代。
full gc:minor gc时Survivor区域不足以容纳年轻代中存活下来的对象时,且老年代中剩余空间容纳不了新生代中存活下来的对象时会进行full gc。老年代中因为没有进行分区,所以回收算法使用的是标记-清理算法或者标记整理算法。
4.gc日志解读