堆
堆的核心概述
-
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
-
Java堆区在JVM启动的时候即被创建,其空间大小也就确定了,堆是JVM管理的最大一块内存空间,并且堆内存的大小是可以调节的。
-
《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
物理内存与逻辑上的内存可以建立一个映射表 -
所有的线程共享Java堆,**在这里还可以划分线程私有的缓冲区(**Thread Local Allocation Buffer,TLAB)。
- 一个进程只有一个JVM,堆针对一个JVM进程来说是唯一的。进程包含多个线程,他们是共享同一堆空间的
但是不是整个堆空间都被共享的,因为有TLAB。 - 搞TLAB的原因:如果所有数据共享则会有线程安全问题,同步处理的话并发性就会变差。所以在堆空间里分出来一部分空间给每个线程分一块,这样并发性更好。
- 一个进程只有一个JVM,堆针对一个JVM进程来说是唯一的。进程包含多个线程,他们是共享同一堆空间的
-
《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)
但是从实际角度看,并不是所有的对象实例都是在堆上分配的,因为还有一些对象是在栈上分配的(逃逸分析,标量替换) -
数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
-
在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
-
堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
堆内存细分
-
Java7 以前堆内存逻辑上分为:新生区+养老区+(永久区)
-
Java8 以后堆内存逻辑上分为:新生区+养老区+(元空间)
-
约定:
新生区 <–> 新生代 <–> 年轻代
养老区 <–> 老年区 <–> 老年代
永久区 <–> 永久代 -
之所以是逻辑上,是因为堆实际上管的只有新生区+养老区
可以看到HEAP -Xmx 内存分配也只是新生区+养老区的总和
设置堆大小与OOM
设置堆大小
- 设置堆大小
Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项"-Xms"和"-Xmx"来进行设置。- -Xmssize 起始内存
-Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize- -X 是jvm的运行参数
- ms是memory start
- size就是设置的堆的起始内存为多少
默认不写单位就是字节,也可以写上k\m\g来表示相应的单位
- -Xmxsize 表示堆区的最大内存
-Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
- -Xmssize 起始内存
开发中建议将初始堆内存和最大堆内存设置成相同的值
频繁的扩容和释放会造成系统不必要的压力
例如我们将两个值都设为600M
-
查看设置的参数
-
方式一:jps 和jstat -gc 进程id
jps #查看当前进程 jstat -gc 进程id
我们这里参数指定的大小实际上就是下面两部分加起来 就是600M
但我们代码中得到的是575M,这是因为绿色的那个框没算进去,JVM 认为幸存者 to 区并不存放对象(to 区一直为空),所以没把它算上。即新生区的大小 = 伊甸园区大小 + 幸存者 from 区大小。
-
方式二:设置虚拟机参数 -XX:+PrintGCDetails
打印GC过程中的细节
-
-
默认堆大小
默认情况下:
初始内存大小:物理电脑内存大小/64
最大内存大小:物理电脑内存大小/4
java代码获取默认堆大小