Java程序在运行时创建的所有类实例或数组都放在同一个堆中。一个Java虚拟机实例中只存在一个堆空间, 因此所有线程都将共享这个堆,它在虚拟机启动时创建。 又由于一个Java程序独占一个Java虚拟机实例, 因而每个Java程序都有它自己的堆空间——它们不会彼此干预 。但是同一个Java程序的多个线程却共享着同一个堆空间,在这种情况下,就得考虑多线程访问对象(堆数据)的同步问题了。
堆在JVM启动时创建。该内存区域存放了对象实例及数组(所有new的对象)。其大小通过-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的值设成一样。
Java虚拟机有一条在堆中分配新对象的指令, 却没有释放内存的指令。正如你无法用Java代码去明确释放一个对象一样,字节码指令也没有对应的功能。虚拟机自已负责决定如何以及何时释放不再被运行的程序引用的对象所占据的内存 。程序本身不用去考虑何时需回收对象所占用的内存,通常虚拟机把这个任务交给垃圾收集器。由于Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆'' 。
和方法区一样, 堆空间也不必是连续的内存区。 在程序运行时, 它可以动态扩展或收缩。事实上, 一个实现的方法区可以在堆顶实现。 换句话说, 就是当虚拟机需要为一个新加载的类分配内存时, 类型信息和实际对象可以都在同一个堆上。 因此, 负责回收无用对象的垃圾收集器可能也要负责无用类的释放(卸载)。另外,某些实现可能也允许用户或程序员指定堆的初始大小、最大最小值等等
由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。
新生代主要存储新创建的对象和尚未进入老年代的对象。程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小。
老年代存储经过多次新生代GC(Minor GC)任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:
1、大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。
2、大的数组对象,切数组中无引用外部对象。 老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。
Young Generation | 即图中的Eden + From Space + To Space |
Eden | 存放新生的对象 |
Survivor Space | 有两个,存放每次垃圾回收后存活的对象 |
Old Generation | 或者叫Tenured Generation 即图中的Old Space 主要存放应用程序中生命周期长的存活对象 |
参考:
http://www.hollischuang.com/archives/80