JVM运行时数据区篇(堆空间基本概述)

Posted ProChick

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM运行时数据区篇(堆空间基本概述)相关的知识,希望对你有一定的参考价值。

1.什么是堆空间?

  • 一个Java程序进程对应一个JVM实例、一个运行时数据区( Runtime类 ),同时又包含多个线程,这些线程共享唯一的方法区和堆空间,每个线程又包含了程序计数器、本地方法栈和虚拟机栈
  • 每一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域
  • 堆区在JVM启动的时候就被创建,其空间大小也就确定了,是JVM管理的最大一块内存空间
  • 堆内存的大小是可以调节的
  • JVM规范表明,堆可以处于物理上不连续的内存空间中。但在逻辑上它应该被视为连续的
  • JVM规范表明,所有对象实例以及数组都应当在运行时分配在堆上。但从实际使用的角度看,只能说“几乎”所有对象实例以及数组都被分配在堆中,但也有可能分配在栈上
  • 数组或对象永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置( 狭义看法 )
  • 堆空间被所有的线程所共享,但其实在堆空间中还可以划分出来线程私有的缓冲区( TLAB:Thread Local Allocation Buffer )。所以严格意义上来说,堆空间不一定是所有线程共享的, 因为TLAB线程私有的缓冲区在堆空间中是独有的
  • 在方法结束后,堆中的对象不会马上被移除,而是在垃圾收集的时候才会被移除
  • 堆空间是GC( Garbage Collection )执行垃圾回收的重点区域

2.堆存储程序示例

  • 对象在堆中的存储情况

    public class SimpleHeap {
        public static void main(String[] args) {
            SimpleHeap sl = new SimpleHeap(1);
            SimpleHeap s2 = new SimpleHeap(2);
        }
    }
    

  • 查看进程的堆内存大小

    public class HeapDemo {
        public static void main(String[] args) {
            System.out.println("start...");
            
            try {
                Thread.sleep(1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("end...");
        }
    }
    

    设置堆内存的大小

    使用工具查看:在JDK的bin目录下有个叫做 jvisualvm 的可执行程序

3.堆的内存结构

  • JDK7以及之前的版本中

    这里指的是逻辑上的划分,因为永久代实际上是在方法区落地实现的

    分区名别名英文名
    新生区新生代、年轻代Young Generation Space
    养老区老年区、老年代Tenure generation Space
    永久区永久代Permanent Space

  • JDK8以及之后的版本中

    这里指的是逻辑上的划分,因为元空间实际上是在方法区落地实现的

    分区名别名英文名
    新生区新生代、年轻代Young Generation Space
    养老区老年区、老年代Tenure generation Space
    元空间元空间Meta Space

4.堆的内存设置

  • 堆空间的内存大小在JVM启动时就已经设定好了,默认情况下:初始内存大小为( 主机物理内存大小/64 )、最大内存大小为( 主机物理内存大小/4 )

  • 我们还可以手动的通过 “-Xmx” 和 “-Xms” 两个JVM参数来进行设置

    在实际开发中,通常会将这两个参数配置相同的值,其目的就是为了能够在Java垃圾回收机制清理完堆区后不需要重新计算堆区的大小、重新调整堆的大小,从而提高用户访问性能

    # 设置堆的起始内存,等价于-XX:InitialHeapSize
    -Xms666m
    
    # 设置堆的最大内存,等价于-XX:MaxHeapSize
    -Xmx666m
    
  • 查看堆内存的分配情况

    通过代码查看

    public class HeapMemory{
        public static void main(String[] args) {
            // 返回Java虚拟机初始化堆内存总量
            long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
            // 返回Java虚拟机最大堆内存总量
            long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
    		
            // 打印数据
            System.out.println("-Xms : " + initialMemory + "M");
            System.out.println("-Xmx : " + maxMemory + "M");
            System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");
            System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G");
        }
    }
    

    通过命令查看

    控制台打印

  • 一旦堆空间中的内存大小超过了它所指定的最大内存时,将会抛出OOM异常

    public class TestOverFlow {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
    
            while(true){
                list.add(new String("hello oom"));
            }
        }
    }
    

5.堆空间的分代

  • 我们可以把存储在JVM中的Java对象划分为两类

    • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
    • 一类是生命周期非常长的对象,在某些极端情况下还可能与JVM的生命周期保持一致
  • 存储在年轻代 Eden 区的对象如果发现没有被 GC 回收,那么它们会被转移到 Survivor0 区或 Survivor1 区存储,如果 Survivor 区的对象也没有被 GC 回收则最终会将它转移到 OldGen

  • 配置新生代与老年代在堆结构的占比

    • 在HotSpot VM中,新生代和老年代的空间默认占比为是 1 : 2

      主要通过 -XX:NewRatio 参数设置,例如: -XX:NewRatio=4,代表新生代占1/5,老年代占4/5,默认 -XX:NewRatio=2

    • 在HotSpot VM中,Eden空间和另外两个Survivor空间默认的占比为是 8 : 1 : 1( 官网所说 ),但实际上是 6 : 1 : 1

      主要通过 -XX:SurvivorRatio 参数设置,默认 -XX:SurvivorRatio=8

    • 几乎所有的Java对象都是在 Eden 区被创建出来的,而绝大部分的Java对象又都销毁在新生代了

    • 我们还可以使用参数 -Xmn 设置新生代最大内存大小,,但这个参数一般使用默认值就好了

以上是关于JVM运行时数据区篇(堆空间基本概述)的主要内容,如果未能解决你的问题,请参考以下文章

JVM运行时数据区篇(堆空间进阶掌握)

JVM运行时数据区篇(堆空间扩展知识)

JVM运行时数据区篇(虚拟机栈)

JVM运行时数据区篇(基础认知)

JVM运行时数据区篇(程序计数器)

JVM运行时数据区篇(本地方法栈)