zoukankan      html  css  js  c++  java
  • 堆和堆参数调优

    一、再议永久代和方法区

      上一篇提到:在8版本以前,JVM采用堆空间的一部分实现方法区,这部分堆空间被称为“永久代”,由于类的结构信息和运行时常量池是放在方法区的,使用永久代实现方法区容易导致堆内存溢出。在8版本推出以后,Java采用了堆外内存即本机物理内存实现方法区,我们把这部分空间称为“元空间”。

      

      

    二、堆内存

      众所周知,当线程new了一个对象后,对象的引用变量存放在栈针中,而对象本身存储在堆内存中,如果大量的对象存储在堆内存而无法被GC回收,那么将导致OOM错误,那么堆空间多大呢?GC的工作流程是什么样呢?可以设置堆空间大小吗?可以选择GC工作方式吗?

      1、堆内存结构以及GC过程:

      

       新生代空间:老年代空间 = 1 : 3,而其中新生代中Eden : from :to区域=8:1:1

      新生代是实例对象诞生的区域,也是绝大多数实例对象消亡的区域,新生代分为两个区域:Eden区和Survivor区,Survivor区又分为SurvivorFrom区和SurvivorTo区。所有的对象都是在Eden区被new出来的。当Eden区满时会触发第一次GC,把幸存下来的对象会被 “复制”到From区 。如果Eden区再此触发GC,此时GC会扫描Eden区和From区,对这两个区域进行垃圾回收,经过这次回收后将Eden和From中还存活的对象会直接复制到To区,并为这些对象的“年龄”+1。 

      在完成复制后,清空Eden和From区的对象。完成清空后,将From去和To去对换,哪个区域为空哪个区域作To区,所以必须这两块区域的大小是一样的。完成互换后,下一次触发GC继续扫描Eden和From区,并再次幸存对象放入To区,清空Eden和From区,然后交换From和To,谁空谁作To,为下一次垃圾回收扫描做准备。当一个对象在From区To区来回复制,“年龄”不断增长始终无法被回收时,一旦“年龄”达到MaxTenuringThreshold的默认值15,此对象就会进入老年代。

      由于每次清空后都会执行From区和To区的调换,所以From区存的下的内容,To区也必须存的下,因此双方的空间大小要一致。也因为每次都是存有对象的幸存区作为From区,因此在GC开始的时候,对象只会存在于Eden区和名为From的Survivor区,Survivor区的To区是空的。所以当GC执行时,Eden区的所有存活对象都会被赋值到To区,而在From区中,仍存活的对象会根据他们的年龄值来决定去向,年龄一旦达到阈值,就会被移动到老年代中,没有达到阈值的对象会被复制到To区。经过前面的操作后,Eden区和From区已经被清空了,这时From区和To区会交换他们的角色,也就是说新的To区是上一次GC的From区,如果在多次GC后,To区被填满了,会将所有的对象移动到老年代中。

      

       黄色为被GC回收的资源空间,红色为存活对象资源,绿色为闲置空间。

      这个过程中的复制操作涉及到GC四大算法中的“复制算法”,将在下一篇文章中介绍。

      2、堆内存调优

      Java虚拟机在启动时,初始内存为本机内存的1/64,当此内存不够实用,虚拟机会尝试获取更多的内存空间,但是默认情况下最大可使用内存为本机内存大额1/4。当然你可以通过配置虚拟机参数来改变虚拟机占用内存的情况,在生产环境中,建议配置初始内存空间大小和最大可使用空间大小相同,以避免进程争夺内存资源时造成的卡顿。

      可以使用如下代码查看虚拟机占用空间的大小:

    public class Demo1 {
        public static void main(String[] args) {
            System.out.println(Runtime.getRuntime().availableProcessors());//查看逻辑处理器
    
            long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机可以使用的最大内存量
            long totalMemory = Runtime.getRuntime().totalMemory();//返回Java虚拟机中的内存总量
    
            System.out.println("-Xmx: MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double)1024/1024) + "MB");
            System.out.println("-Xms: TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double)1024/1024) + "MB");
        }
    }
    4
    -Xmx: MAX_MEMORY = 1886912512(字节)、1799.5MB
    -Xms: TOTAL_MEMORY = 128974848(字节)、123.0MB

      3、配置虚拟机占用内存大小

      IDEA:

      

      STS

      

       参数:

      -Xms:设置初始(start)分配内存大小  -Xmx:设置最大(max)分配内存大小  -XX:+PrintGCDetails:输出详细的GC处理日志

      4、制造错误、日志分析

      为了更快地导致内存溢出,我将-Xms和-Xmx都设置成10m。

    public class Person {
        private String name;
        byte[] bytes = new byte[10*1024*1024];
        public Person(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    public class Demo1 {
        public static void main(String[] args) {
            while(true){
                Person person = new Person(UUID.randomUUID().toString());
            }
        }
    }
    [GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->696K(9728K), 0.0032340 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 1289K->504K(2560K)] 1481K->812K(9728K), 0.0006781 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 504K->488K(2560K)] 812K->852K(9728K), 0.0006038 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (Allocation Failure) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 364K->774K(7168K)] 852K->774K(9728K), [Metaspace: 4020K->4020K(1056768K)], 0.0075233 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 774K->774K(9728K), 0.0003232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 774K->753K(7168K)] 774K->753K(9728K), [Metaspace: 4020K->4020K(1056768K)], 0.0113270 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at com.qlu.juc.Person.<init>(Person.java:5)
        at com.qlu.juc.Demo1.main(Demo1.java:8)
    Heap
     PSYoungGen      total 2560K, used 99K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
      eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd18d90,0x00000000fff00000)
      from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
      to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
     ParOldGen       total 7168K, used 753K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
      object space 7168K, 10% used [0x00000000ff600000,0x00000000ff6bc510,0x00000000ffd00000)
     Metaspace       used 4051K, capacity 4572K, committed 4864K, reserved 1056768K
      class space    used 454K, capacity 460K, committed 512K, reserved 1048576K

      ①

     

     在分配内存是,我指定了虚拟机最大占用内存为10m,而此处仅新生代和老年代就占用了近乎10m,足以证明元空间并不使用虚拟机内存实现,而是使用了本地内存。

      ②分析日志:内存占用

       ③分析日志:GC日志

    [GC (Allocation Failure) [PSYoungGen: 1289K->504K(2560K)] 1481K->812K(9728K), 0.0006781 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

    [GC (Allocation Failure)
    指出了GC类型,GC负责收集新生代的Eden和From区域,Allocation Failure表示空间分配失败
    [PSYoungGen: 1289K->504K(2560K)]
    1289k表示YoungGC收集前新生代内存占用情况,504k表示YoungGC后新生代内存占用情况,此轮GC释放了785k内存。(2560)表示新生代总占用空间大小
    1481K->812K(9728K), 0.0006781 secs]
    1481k表示YoungGC收集前堆的使用情况,812k表示YoungGC收集之后堆空间的使用情况,次轮GC释放了669k内存。(9728k)表示堆空间的总大小,还有10m-9728k被栈、程序计数器、本地方法栈占用。
    0.0006781secs表示YoungGC耗时。
    [Times: user=0.00 sys=0.00, real=0.00 secs]
    user:YoungGC用户耗时,sys:YoungGC 系统耗时,real:YoungGC实际耗时

    [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 774K->753K(7168K)] 774K->753K(9728K), [Metaspace: 4020K->4020K(1056768K)], 0.0113270 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 

    [Full GC (Allocation Failure)
    指出了GC类型,Full GC负责收集老年代区域,Allocation Failure表示空间分配失败
    [PSYoungGen: 0K->0K(2560K)]
    新生代:GC前后Young区内存占用情况
    [ParOldGen: 774K->753K(7168K)]
    老年代:GC前老年代的占用情况,可以看出基本没有回收,7168k表示老年代内存空间大小
    774K->753K(9728K),
    GC前后堆的占用情况以及对的总内存大小
    [Metaspace: 4020K->4020K(1056768K)], 0.0113270 secs]
    元空间占用情况和元空间大小,Full GC耗时
    [Times: user=0.02 sys=0.00, real=0.01 secs]
    user:YoungGC用户耗时,sys:YoungGC 系统耗时,real:YoungGC实际耗时。

    当老年代空间执行了Full GC后发现依然无法进行新对象的保存(空间分配)即Allocation Failure,就会报出“OutOfMemoryError”。

  • 相关阅读:
    Spring RestTemplate 之put、delete请求
    Too many connections解决方案
    各个平台的mysql重启命令
    MySQL出现too many connections(1040)错误解决方法
    EXCEL中,如何引用一个单元格中的数据,作为另一个单元格内容中的一部分?
    [翻译][Java]ExecutorService的正确关闭方法
    MySQL:日期函数、时间函数总结(MySQL 5.X)
    MySQL 获得当前日期时间 函数
    线程本地变量ThreadLocal
    split 分割 字符串(分隔符如:* ^ : | , . ?) 及注意点
  • 原文地址:https://www.cnblogs.com/superlsj/p/11671675.html
Copyright © 2011-2022 走看看