JVM内存管理机制及参数设置和跟踪参数

 

  • 运行时数据区域

    • 线程共享内存区

      • Java堆

        • Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”
        • Java堆内存大小可通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小Heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样
        • 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
        • 分为新生代和老年代
          • 新生代
            • 程序新创建的对象一般都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成
            • 可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小比列
          • 老年代
            • 用于存放经过多次新生代GC任然存活的对象,例如缓存对象
            • 新建对象,直接进入老年代情况:
              • 新建的大对象,可通过-XX:PretenureSizeThreshold(单位为字节,默认为0)参数来判断是否为大对象
              • 大的数组对象,且数组中无引用外部对象。老年代内存等于Java堆内存减去新生代内存
      • 方法区

        • 概述
          • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
          • 方法区分配内存可以不连续,可以动态扩展
          • 垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代(-XX:PermSize,设置持久代初始值,一般设置为物理内存的1/64;-XX:MaxPermSize,设置持久代最大值,一般设置为物理内存的1/4)的名字一样“永久”存在。在该区域进行内存回收的主要目的是对常量池的回收和对内存数据的卸载;一般来说这个区域的内存回收效率比起 Java 堆要低得多
          • 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
        • 运行时常量池(方法区的一部分)
          • 保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中
          • 运行时常量池相对于 Class常量池一大特征就是其具有动态性,Java 规范并不要求常量只能在运行时才产生,也就是说运行时常量池中的内容并不全部来自 Class常量池,Class常量池并非运行时常量池的唯一数据输入口;在运行时可以通过代码生成常量并将其放入运行时常量池中
          • 当运行时常量池无法申请到内存时,将抛出OutOfMemoryError异常
    • 线程私有内存区

      • Java虚拟机栈

        • 概述
          • 每一个线程都有一个独自的Java虚拟机栈,它的生命周期和线程相同
          • 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息
          • 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
          • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
        • Java虚拟栈组成

          • 局部变量区
            • 局部变量区被组织为以一个字长为单位、从0开始计数的数组,存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。类型为short、byte和char的值在存入数组前要被转换成int值,其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。(在访问局部变量中的long或double时,只需取出连续两项的第一项的索引值即可,如某个long值在局部变量区中占据的索引时3、4项,取值时,指令只需取索引为3的long值即可)
          • 操作数栈
            • 和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的。可把操作数栈理解为存储计算时,临时数据的存储区域。
            • 演示
              • 代码
                public static int add(int a,int b){
                int c=0;
                c=a+b;
                return c;
                }​​
              • 演示图片

                JVM内存管理机制及参数设置和跟踪参数

              • 具体操作过程
                 0:   iconst_0 // 0压栈
                 1:   istore_2
                // 弹出int,存放于局部变量2
                 2:   iload_0  //
                把局部变量0压栈
                 3:  
                iload_1 // 局部变量1压栈
                 4:   iadd      //弹出2个变量,求和,结果压栈
                 5:   istore_2
                //弹出结果,放于局部变量2
                 6:   iload_2  //局部变量2压栈
                 7:   ireturn  
                //返回
          • 帧数据区
            除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。
            当JVM执行到需要常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。
            除了处理常量池解析外,帧里的数据还要处理java方法的正常结束和异常终止。如果是通过return正常结束,则当前栈帧从Java栈中弹出,恢复发起调用的方法的栈。如果方法有返回值,JVM会把返回值压入到发起调用方法的操作数栈。
            为了处理java方法中的异常情况,帧数据区还必须保存一个对此方法异常引用表的引用。当异常抛出时,JVM给catch块中的代码。如果没发现,方法立即终止,然后JVM用帧区数据的信息恢复发起调用的方法的帧。然后再发起调用方法的上下文重新抛出同样的异常。
        • 特性
          • 栈上分配
            • 小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
            • 直接分配在栈上,可以自动回收,减轻GC压力
            • 大对象或者逃逸(引用)对象无法栈上分配
      • 本地方法栈

        • 本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务
        • 本地方法栈区域也会抛出*Error和OutOfMemoryError异常
      • 程序计数器

        • 当前线程所执行字节码的行号指示器
        • 线程执行Java方法,计数器记录的是正在执行的虚拟机字节码指令地址;若线程执行的是本地方法(Native Method),计数器的值为空(Undefined)
        • 该内存区域是唯一一个在Java虚拟机规范中没有规定OutOfMemoryError的区域
  • 直接内存

    • 直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域
    • 在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据
    • 当各个内存区域总和大于物理内存,也会抛出OutOfMemoryError
  • 常用JVM跟踪配置参数

    • Trace跟踪参数

      • -verbose:gc -XX:+printGC
        • 可以打印GC的简要信息
          –[GC4790K->374K(15872K), 0.0001606 secs]
          –[GC4790K->374K(15872K), 0.0001474 secs]
      • -XX:+PrintGCDetails
        • 打印GC详细信息

          JVM内存管理机制及参数设置和跟踪参数

      • -XX:+PrintGCTimeStamps
        • 打印GC发生的时间戳
          –[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs]
          ​ [Times: user=0.00 sys=0.00, real=0.00 secs] 
      • -Xloggc:log/gc.log
        • 1、指定GC log的位置,以文件输出。2、帮助开发人员分析问题
      • -XX:+PrintHeapAtGC
        • 每一次GC过后,都打印堆信息

          JVM内存管理机制及参数设置和跟踪参数

      • -XX:+TraceClassLoading
        • 监控类的加载

          JVM内存管理机制及参数设置和跟踪参数

      • -XX:+PrintClassHistogram
        • 按下Ctrl+Break后,打印类的信息:

          JVM内存管理机制及参数设置和跟踪参数

        • 分别显示:序号、实例数量、总大小、类型
    • 堆的分配参数

      • -Xmx –Xms
        • 指定最大堆和最小堆
      • -Xmn
        • 设置新生代大小
      • -XX:NewRatio
        • 新生代(eden+2*s)和老年代(不包含永久区)的比值
          4 表示 新生代:老年代=1:4,即年轻代占堆的1/5
      • -XX:SurvivorRatio
        • 设置两个Survivor区和eden的比
          8表示 两个Survivor :eden=2:8,即一个Survivor占年轻代的1/10
      • -XX:+HeapDumpOnOutOfMemoryError
        • OOM时导出堆到文件
      • -XX:+HeapDumpPath
        • 导出OOM的路径
      • -XX:OnOutOfMemoryError
        • 在OOM时,执行一个脚本

          JVM内存管理机制及参数设置和跟踪参数

      • 总结
        • 根据实际情况调整新生代和幸存代的大小
        • 官方推荐新生代占堆的3/8
        • 幸存代占新生代的1/10
        • 在OOM时,记得Dump出堆,确保可以排查现场问题
    • 永久区分配参数

      • -XX:PermSize  -XX:MaxPermSize
        • 设置永久区的初始空间和最大空间
        • 表示一个系统可以容纳多少个类型
    • 栈大小分配

      • -Xss
        • 通常只有几百K
        • 决定了函数调用的深度
        • 每个线程都有独立的栈空间
        • 局部变量、参数分配在栈上