一、堆
1、介绍
Java运行程序对应一个进程,一个进程就对应一个JVM实例。一个JVM实例就有一个运行时数据区(Runtime),Runtime里面,就只有一个堆,一个方法区。这里也阐述了,方法区和堆是一个进程一份。而一个进程当中,可以有多个线程,那就意味着一个进程中的多个线程会共享堆空间和方法区。
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。堆在JVM启动的时候被创建,其空间大小也就确定了,是JVM管理的最大一块内存空间,堆内存大小是可以调节的。
Java虚拟机规范规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(TLAB)。
堆空间中,有一部分线程私有的缓冲区,叫TLAB,它不是所有线程共享的区域。
《Java虚拟机规范》中对堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。其实,从实际使用角度来看,是"几乎"所有的对象实例都在这里分配内存。
数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或数组在堆中的位置。在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。堆,是GC执行垃圾回收的重点区域。而频繁的GC会影响用户线程的执行。
为什么是几乎?逃逸分析,会去判断在方法中对象是否发生了逃逸,如果没有的话,会在栈上分配。
2、堆的内存结构
堆空间细分
JDK7:
JDK8:
永久代-->元空间
3、设置堆内存大小与OOM
Java堆用于存储Java对象实例,在JVM启动时就已经设定好了。可以通过-Xmx和-Xms来进行设置。一旦堆区的内存大小超过-Xmx所指定的最大内存时,将会抛出OutOfMemoryError异常。
通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分割计算堆区的大小,从而提高性能。
理由:初始设置一个值之后,如果堆空间不够的话,需要不断的扩容。后续不用的话,也需要释放。那么,在服务器使用的时候,堆空间不断的扩容。在空闲的时候,也需要把堆空间做释放,那频繁的扩展和释放,会造成不必要的系统压力。
默认情况下,初始内存大小:物理电脑内存大小/64。最大内存:物理电脑内存大小/4。
代码示例:设置堆内存大小
1 // 默认情况 2 public class Main { 3 public static void main(String[] args) { 4 5 // 返回Java虚拟机中的堆内存总量 6 long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024; 7 // 返回Java虚拟机试图使用的最大堆内存量 8 long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024; 9 10 System.out.println("-Xms : " + initialMemory + "M"); 11 System.out.println("-Xmx : " + maxMemory + "M"); 12 13 System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G"); 14 System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G"); 15 16 // try { 17 // Thread.sleep(1000000); 18 // } catch (InterruptedException e) { 19 // e.printStackTrace(); 20 // } 21 } 22 } 23 24 // 默认值 25 // -Xms : 245M (约为 16G / 64) 26 // -Xmx : 3628M 27 // 系统内存大小为:15.3125G (这个值约等于系统内存) 28 // 系统内存大小为:14.171875G 29 30 // 设置堆参数:-Xms600m -Xmx600m 31 // -Xms : 575M 32 // -Xmx : 575M