内存回收策略

1、对象优先在Eden里分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minar GC。

在JDK8环境上面分析一下日志:

import java.util.HashSet;
import java.util.Set;

/**
 * 优先分配eden园区
 * jvm args -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+UseConcMarkSweepGC
 */
public class EdenFirst {

    public static void main(String[] args) throws InterruptedException {
        Set<byte[]> ss = new HashSet<>();
        for (int i = 1; i <= 200; i++) {
            ss.add(new byte[1024 * 1024]);
        }
    }

}

 2、大对象直接进入老年代
大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组,byte[]数组就是典型的大对象。大对象对虚拟机的内存分配来说就是一个不折不扣的坏消息,比遇到一个大对象更加坏的消息就是遇到一群"朝生夕灭”的"短命大对象”,我们写程序的时候应注意避免。

在Java虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复制对象时,大对象就意味着高额的内存复制开销。HotSpot虚 拟机提供了-XX: PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。

注意:-XX: PretenureSizeThreshold参数只对Serial和ParNew两款新生代收集器有效,HotSpot的其他新生代收集器,如Parallel Scavenge并不支持这个参数。如果必须使用此参数进行调优,可考虑ParNew加CMS的收集器组合。

import java.util.HashSet;
import java.util.Set;

/**
 * 大对象可以放在老年代,避免在eden,from,to新生代区域来回切换,-XX:PretenureSizeThreshold=2M
 * jvm args
 * -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+UseConcMarkSweepGC -XX:PretenureSizeThreshold=2M
 */
public class PretenureSizeThresholdTest {
    public static void main(String[] args) throws InterruptedException {
        Set<byte[]> ss = new HashSet<>();
        for(int i=1;i<=10;i++){
            // ss.add(new byte[1024*1024]);
            ss.add(new byte[1024*1024*3]);
        }
    }
}

 3、长期存活的对象将进入老年代
HotSpot虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。为做到这点,虚拟机给每个对象定义了一个对象年龄(Age) 计数器,对象通常在Eden区里诞生,如果经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

import java.util.HashSet;
import java.util.Set;

/**
 * 大对象可以放在老年代,避免在eden,from,to新生代区域来回切换,-XX:PretenureSizeThreshold=2M
 * jvm args
 * -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+UseConcMarkSweepGC -XX:PretenureSizeThreshold=2M
 */
public class PretenureSizeThresholdTest {
    public static void main(String[] args) throws InterruptedException {
        Set<byte[]> ss = new HashSet<>();
        for(int i=1;i<=10;i++){
            // ss.add(new byte[1024*1024]);
            ss.add(new byte[1024*1024*3]);
        }
    }
}

 这里还有一个经典问题:"premature promotion"(过早提升)

import java.util.HashSet;
import java.util.Set;

/**
 * 简单来说是幸存者区不够了,然后提前晋升到了老年代,一次没得问题,次数多了,累计起来会耗光老年代的资源然后晋升老年代失败导致的OOM
 * -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintHeapAtGC
 * -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution -XX:MaxTenuringThreshold=15
 */
public class PrematurePromotion {

        public static void main(String[] args) throws InterruptedException {
            Set<byte[]> ss = new HashSet<>();
            for(int i=1;i<=140*1024;i++){
                ss.add(new byte[1024]);
            }
        }

}

 

 

解决办法:

需要观察多次 Minor GC 过程, 看 Minor GC 后存活下来的对象大小,最终确定 Survivor 的合适大小。 整个调优过程可能需要几次调整, 才能找到比较合适的值。调整几次后, 如果内存还是不够用, 就要需要考虑增大服务器内存, 或者把负载分担到更多的 JVM 实例上。

4、动态对象年龄判定
为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX: MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX: MaxTenuringThreshold中要求的年龄。

import java.util.HashSet;
import java.util.Set;

/**
 * 为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX: MaxTenuringThreshold才能晋升老年代,
 * 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,
 * 无须等到-XX: MaxTenuringThreshold中要求的年龄。
 * jvm args -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+UseConcMarkSweepGC
 * -XX:+PrintTenuringDistribution -XX:MaxTenuringThreshold=15
 */
public class DynamicTenuring {
    public static void main(String[] args) throws InterruptedException {
        Set<byte[]> ss = new HashSet<>();
        for(int i=1;i<=4*1024;i++){
            byte[] bb = new byte[1024*1024];
            bb = null;
            ss.add(new byte[1024]);
        }
    }
}

 

posted @ 2021-10-07 22:07  *乐途*  阅读(95)  评论(0)    收藏  举报