jvm内存模型
1.内存模式概览:
2.主要参数设置:
堆:初始化堆内存-Xms 最大堆内存 -Xmx 新生代:-Xmn 如:Xmx2048M
元空间:元空间使用的是直接内存,默认大小是21M,超过该值会触发FullGC,同时会扩容,因此为了启动时,更快,需要设置该值:
- XX:MaxMetaspaceSize 最大元空间,默认-1 代表只跟本地内存大小有关
-XX:MetaspaceSize 初始化元空间 达到该值会触发FullGC,默认是21M
由于调整元空间会触发fullGC,那么建议两者大小设置一致,8G内存建议设置为512M
如:-XX:MetaspaceSize=512M
线程栈:-Xss128k 代表设置某个线程栈的总大小是128k,默认是1M,值越大,那么该线程能够调用的方法深度越深,值越小,则能创建的线程数越多,下面举个列子:
在Xss128k的情况下:
将其改成1M再测试:
面试题:能否对JVM调优,让其几乎不发送FullGC?答案是能的,将年轻代设置大一点就可以了
3.对象的创建过程
内存分配:
1.指针碰撞(默认)如果内存是规整的,已分配的在一边,未分配的在一边,中间有个指针分割,分配时,只要移动指针到对象大小的空间即可
2.空闲列表: 如果内存是不规整的,此时需要用一个空闲列表来记录哪些内存是可分配的
并发问题:a对象和b对象同时需要分配一块内存,此时怎么解决
1.CAS失败重试方式
2.本地线程分配缓冲区:TLAB:意思是在堆上给每个线程分配一小块内存,该线程创建的对象,优先在该内存中分配,如果内存不足,在使用指针碰撞方式分配
JVM会默认开启XX:+UseTLAB,XX:TLABSize 指定TLAB大小。
初始化:给变量分配默认值
设置对象头:(包含:marker work,类型指针,数组长度):
4.对象指针压缩:
在64位系统中,java虚拟机使用32位的指针,如果没进行压缩,会导致内存增多,占用较大宽带,同时GC也会承受较大压力,32位地址支持最大堆内存是4G,如果内存大于32GB,压缩指针会失效
参数控制:XX:+UseCompressedOops(默认开启),禁止指针压缩:XX:-UseCompressedOops,如果只是对对象头的类型指针进行压缩
‐XX:+UseCompressedClassPointers 默认开启的压缩对象头里的类型指针Klass Pointer
5.对象在堆中分配大概流程:
具体流程描述:
1. 对象分配时,需要判断对象是否能在栈分配(好处是对象的销毁不用经过GC,方法调用完直接销毁,判断的依据:逃逸分析,简单的说,一个对象只在方法中用到,如:
如果对象太大,在栈中分配不下,依然会在堆中创建该对象,栈中如果没有连续的空间来分配,那么就需要用到标量替换,啥意思呢,例如User这个对象有Id和name2个属性,我只要在栈中找到内存分别存Id和name属性来代替User对象;
jdk1.7后是默认开启逃逸分析和标量替换:-XX:+DoEscapeAnalysis(逃逸分析) ‐XX:+EliminateAllocations(标量替换)
2. 大对象直接分配到老年区,如字符串,数组,那到底啥样的才是大对象,可以同过参数来判断:-XX:PretenureSizeThreshold=1000000 (单位是字节) ,这个参数只在 Serial 和ParNew两个收集器下有效。
3. 如果TLAB(线程本地分配缓冲)也就是堆中分配给线程的一小块内存地址,如果能放到下该对象,就存起来,如果放不下,就通过指针碰撞方式,在堆中划分内存
4.如果对象放到Eden区满了,会触发minor GC,对Eden区的对象和其中一个s非空闲区(s0,s1两者永远都有一方是空闲的)的对象进行回收,回收后的非垃圾对象会存到空闲的S区,如果s区放不下或者存活的对象年龄达到了阈值,就会直接进入老年代
‐XX:MaxTenuringThreshold 这个参数可以设置年龄的阈值,最大值是15,CMS垃圾收集器的默认值是6
5.对象动态年龄判断:存放对象的s区,如果一批对象的大小等于该区的50%,找出这批对象的最大年龄,大于等于该年龄的对象都可以进入老年代,可以通过-XX:TargetSurvivorRatio来设置,minor gc之后触发该判断
6. 老年代空间担保机制:
-XX:-HandlePromotionFailure (jdk1.8默认就设置了)的参数是否设置了
7. Eden区和S0,S1的比率默认是8:1:1
参数-XX:+UseAdaptiveSizePolicy默认开启,会导致比率自动变化,可以关闭它