JVM-内存分配机制

  本文章分为两部分,一部分讲述jvm内存分配,还有一部分讲述会被回收类的解析。

  b话不多说,直接进入主题

  一切概念性的东西,代码&图解搞起;

  芜湖起飞~

   

  1.JVM内存分配机制

  开始之前,我们先到idea配置个jvm参数。

public class GcTest {
  //新建一个类,创建一个main函数,什么都不干,然后加入参数   -XX:+PrintGCDetails
    public static void main(String[] args) {

    }

}

  我们来运行一些程序,看看控制台输出

Heap
 PSYoungGen      total 38400K, used 8209K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
  eden space 33280K, 24% used [0x00000000d5f00000,0x00000000d67044f8,0x00000000d7f80000)
  from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
  to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
 ParOldGen       total 87552K, used 0K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
  object space 87552K, 0% used [0x0000000081c00000,0x0000000081c00000,0x0000000087180000)
 Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

  上面输出的是我本地jvm的堆内存信息,可以看到,我的年轻代一共是35MB左右,已使用8MB左右;老年代为85MB左右,使用量为0;剩下的是一些原空间信息。

  1.1对象优先在Eden区分配

  对象在堆内存开辟空间,优先分配在eden区。我们来运行下面代码看看。

public class GcTest {

    public static void main(String[] args) {
        byte[] bytes = new byte[1024*1024*5];
    }

}

  我new了一个5MB的对象,我们看控制台,这个对象被放到那里了:

Heap
 PSYoungGen      total 38400K, used 13329K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
  eden space 33280K, 40% used [0x00000000d5f00000,0x00000000d6c04508,0x00000000d7f80000)
  from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
  to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
 ParOldGen       total 87552K, used 0K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
  object space 87552K, 0% used [0x0000000081c00000,0x0000000081c00000,0x0000000087180000)
 Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

  上面我们可以清楚的看到,new的byte数组被放到了eden区,原本只占24%的eden区现在被占用了40%。由此可见,我们新new的对象默认都是往eden区存放。

  1.2大对象直接进入老年代

  大对象直接进入老年代,什么情况下新new的对象会直接进入老年代呢?我们看下面代码:

public class GcTest {

    public static void main(String[] args) {
        byte[] bytes = new byte[1024*1024*50];
    }

}

  执行上面代码,看控制台输出:

Heap
 PSYoungGen      total 38400K, used 8209K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
  eden space 33280K, 24% used [0x00000000d5f00000,0x00000000d67044f8,0x00000000d7f80000)
  from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
  to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
 ParOldGen       total 87552K, used 51200K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
  object space 87552K, 58% used [0x0000000081c00000,0x0000000084e00010,0x0000000087180000)
 Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

  上面我们可以看出,eden区还是只被占用了默认的24%,但是我们看老年代,已经被使用了51200kb,由此可见,我们上面new 的bytes直接进入到老年代了。

  那么是多大的对象会直接进入到老年代呢?我们把50MB改成30MB看看,被分配到哪:

public class GcTest {

    public static void main(String[] args) {
        byte[] bytes = new byte[1024*1024*30];
    }

}

  执行结果:

Heap
 PSYoungGen      total 38400K, used 8209K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
  eden space 33280K, 24% used [0x00000000d5f00000,0x00000000d67044f8,0x00000000d7f80000)
  from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
  to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
 ParOldGen       total 87552K, used 30720K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
  object space 87552K, 35% used [0x0000000081c00000,0x0000000083a00010,0x0000000087180000)
 Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

  我们可以看到,数组被分配到了老年代,30MB+38400*0.24/1024这个结果是要大于eden区内存的,所以数组直接被分配到了老年代。

  由此,我们可以得出结论:新new 的对象如果比较大,大于eden区剩余空间,这个对象就会被直接分配到老年代。

  再来看jvm控制大对象分配参数:-XX:PretenureSizeThreshold

  我们在idea加上这个参数:-XX:PretenureSizeThreshold=5m -XX:+UseSerialGC(为啥要加后面这个参数呢?因为大对象直接进老年代只有在Serial 和ParNew两个收集 器下有效)

  我们把bytes改成6MB:

public class GcTest {

    public static void main(String[] args) {
        byte[] bytes = new byte[1024*1024*6];
    }

}

  运行结果:

Heap
 def new generation   total 39296K, used 8440K [0x0000000081c00000, 0x00000000846a0000, 0x00000000abd50000)
  eden space 34944K,  24% used [0x0000000081c00000, 0x000000008243e2d0, 0x0000000083e20000)
  from space 4352K,   0% used [0x0000000083e20000, 0x0000000083e20000, 0x0000000084260000)
  to   space 4352K,   0% used [0x0000000084260000, 0x0000000084260000, 0x00000000846a0000)
 tenured generation   total 87424K, used 6144K [0x00000000abd50000, 0x00000000b12b0000, 0x0000000100000000)
   the space 87424K,   7% used [0x00000000abd50000, 0x00000000ac350010, 0x00000000ac350200, 0x00000000b12b0000)
 Metaspace       used 3321K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

  bytes直接被分配到了老年代。

  1.3长期存活的对象将进入老年代

  如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象 在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定 程度(默认为15岁),就会被晋升到老年代中。可以通过参数:-XX:MaxTenuringThreshold 来控制分带年龄进入老年代大小。

  我们先把上面大对象去老年代参数去掉,然后添加参数:-Xms180M -Xmx180M -XX:NewSize=60M -Xmn60M;

  然后运行空的main函数,可以看到下面结果:

Heap
 PSYoungGen      total 53760K, used 9232K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
  eden space 46080K, 20% used [0x00000000fc400000,0x00000000fcd04380,0x00000000ff100000)
  from space 7680K, 0% used [0x00000000ff880000,0x00000000ff880000,0x0000000100000000)
  to   space 7680K, 0% used [0x00000000ff100000,0x00000000ff100000,0x00000000ff880000)
 ParOldGen       total 122880K, used 0K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
  object space 122880K, 0% used [0x00000000f4c00000,0x00000000f4c00000,0x00000000fc400000)
 Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

  我们可以看到eden区大小为46080kb,我们要写一个程序,搞点对象,让对象发生15次gc。

public class GcTest {


    static byte[] bytes = new byte[1024*1024*6];

    //-XX:+PrintGCDetails
    public static void main(String[] args) {
        //-XX:PretenureSizeThreshold=5m -XX:+UseSerialGC



        //(3*15)*1024 = 46080 = eden区大小46080发生一次gc
        //所以要放入 15*15-2-(46080/1024)*0.2-2*5=204个3MB大小的对象

        for (int i =0;i <= 204; i++) {
            byte[] bs = new byte[1024*1024*3];
            if (i == 204) {
                System.out.println(15+"次gc了");
            }
        }


    }

}

  我们看控制台输出:

[GC (Allocation Failure) [PSYoungGen: 45175K->7678K(53760K)] 45175K->7694K(176640K), 0.0045698 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 51570K->7303K(53760K)] 51586K->7319K(176640K), 0.0062331 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 52104K->7287K(53760K)] 52120K->7311K(176640K), 0.0051494 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 51171K->7287K(53760K)] 51195K->7311K(176640K), 0.0048894 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 51181K->7287K(53760K)] 51205K->7319K(176640K), 0.0056883 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 51187K->7343K(53760K)] 51219K->7375K(176640K), 0.0111028 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 51247K->32K(46592K)] 51279K->7319K(169472K), 0.0107118 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 43938K->0K(50688K)] 51225K->7295K(173568K), 0.0003178 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 40735K->0K(41472K)] 48031K->7295K(164352K), 0.0004945 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 40737K->0K(49664K)] 48032K->7295K(172544K), 0.0003646 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 37625K->0K(50176K)] 44921K->7295K(173056K), 0.0007728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 37626K->0K(50176K)] 44921K->7295K(173056K), 0.0010482 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 37626K->0K(50176K)] 44922K->7295K(173056K), 0.0004554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 37626K->0K(50688K)] 44922K->7295K(173568K), 0.0003000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 37636K->0K(50176K)] 44932K->7295K(173056K), 0.0003343 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
15次gc了
Heap
 PSYoungGen      total 50176K, used 35353K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
  eden space 39424K, 89% used [0x00000000fc400000,0x00000000fe686560,0x00000000fea80000)
  from space 10752K, 0% used [0x00000000fea80000,0x00000000fea80000,0x00000000ff500000)
  to   space 10752K, 0% used [0x00000000ff580000,0x00000000ff580000,0x0000000100000000)
 ParOldGen       total 122880K, used 7295K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
  object space 122880K, 5% used [0x00000000f4c00000,0x00000000f531fed8,0x00000000fc400000)
 Metaspace       used 3357K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

  老年代对象使用空间为5%,122880KB*5%不就正好是6MB吗,不就是我们上面的静态byte数组吗?15次gc到达了老年代!

  1.4对象动态年龄判断

  

猜你喜欢

转载自www.cnblogs.com/ghsy/p/13396557.html