Java技术体系的自动内存管理,最根本的目标是自动化解决两个问题:自动给对象分配内存和 自动回收分配给对象的内存
1. 对象优先在Eden分配
参数 | 解释 |
---|---|
-Xms | 初始堆大小 |
-Xmx | 最大堆大小 |
-XX:NewSize=n | 设置年轻代大小 |
-XX:NewRatio=n | 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 |
-XX:SurvivorRatio=n | 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5 |
-XX:MaxPermSize=n | 设置持久代大小 |
-Xmn2G | 设置年轻代大小为2G。 |
-XX:PermSize | 设置非堆内存初始值,默认是物理内存的1/64; |
1. 为什么要有Survivor区?
设置Sunivor区的意义在哪里?如果没有Sunivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(因为Major GC一般伴随着MinorGc,也可以看做触发了Ful GC)。老年代的内存空间远大于新生代,进行一次Fu GC消[的时间比Minor GC长得多,频发的FUI! GC消耗的时间是非常可观的,这一点会形响理序的执行和响应速度,Sunivor的存在意义:就是减少被送到老年代的对象,进而减少Fu GC的发生,Suryvor的颈筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
2. 为什么要设置两个Survivor区?
设置两个Sunivor区最大的好处就是解决了碎片化,为什么一个Sunivor区不行?假设现在只有一个sunior区:刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor Gc,Eden中的存活对象舰会被移动到Suivor区,这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Sunivor各有一些存活对象,如果此时把Edon区的存活对象硬放到Sunivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。碎片化带来的风险是极大的,堆中没有足够大的连续内存空间,无法为一个内存需求很大的对象分配内存。所以应该建立两块Sunvor区,刚刚新建的对象在Eden中,经历一次Minor GCc,Eden中的存活对象就会被移动到S0,Eden被清空;等Eden区再满了,就再触发一次Mnor GC,Eden和S0中的存活对象又会被复制送入第二块S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Edon两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。
package 内存分配与回收策略;
/**
* @author:左泽林
* @date:日期:2021-09-29-时间:21:06
* @message:
*/
public class demo3_7 {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB];
}
}
我们可以看到对象优先在新生代分配,新生代内存已满了之后,会分配在老年代里面
2. 大对象直接进入老年代
大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组,本节例子中的byte[]数组就是典型的大对象。比遇到大对象更恐怖的就是遇到一个朝生夕灭的‘短命大对象’,我们写程序的时候需要避免。除此之外,大对象往往会触发内存分配,来进行分配他们,这就意味着高额的内存复制开销。
设置参数:
-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=3145728
public class demo3_8 {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
byte[] allocation;
allocation = new byte[9 * _1MB];
}
}
分配的数据会直接分配到老年代。
3. 长期存活的对象进入老年代
对象分配新生代和老年代的分配规则:
为做到这点,虚拟机给每个对象定义了一个对象年龄(Age) 计数器,存储在对象头中(详见第2章)。对象通常在Eden区里诞生,如果经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数==-XX:MaxTenuringThreshold==设置。
4. 动态对象年龄判定
HotSpot虚拟机并不是永远要求对象的年龄必须达到==-XX: MaxTenuringThreshold==才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
5. 空间分配担保
在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保是安全的。
如果不成立,则虚拟机会先查看-XX: HandlePromotionFailure参数的设置值是否允许担保失败( Handle Promotion Failure) ;
如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。
如果大于,将尝试进行一次M inor GC,尽管这次M inor GC是有风险的;
如果小于,或者-XX:HandlePromotionF ailure设置不允许冒险,那这时就要改为进行一次Full GC。