概念
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。在《Java虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配。由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了
所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存
Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常
数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,指向数组和对象在堆中的地址。
方法结束后,堆中的对象不会马上回收,仅仅在垃圾收集的时候被移除。
堆中内存分配
实际上,堆分成新生代和老年代。其-Xms10m和-Xmx10m设置的也是新生代和老年代的大小总和,不包括永久代(在jdk1.8叫元空间)的大小。
堆空间大小的设置和查看
其大小为什么和设置的不一样是s1区只计算了一次,详细看图
查看方式一:
9.5 =(512+2048+7168) / 1024
查看方式二:
新生代和老年代中相关参数的设置
新生代(YoungGen)
老年代(oldGen)
新生代进一步划分的话,可以分为Eden(伊甸园区)、Survivor0(幸存者)、Survivor1区。有一些地方也叫From和To区(动态变化,经YGC后,空的幸存者区就是To区)
新生代和老年代内存大小的默认比例是1 :2,可以通过"-XX:NewRatio"设置。
例如-XX:NewRatio=5表示新生代和老年代的比例是1 :5,新生代占堆内存的1/6。
新生代中Eden和S0、S1的默认比例是8 :1 :1。但因为JVM自动开启了自适应的内存分配策略,导致去查看的时候比例不是8 :1 :1,可以通过参数"-XX:UseAdoptiveSizePolicy"关闭。但可能关闭了比例也不是8 :1 :1。可以通过参数"-XX:SurvivorRatio"显示的设置比例。
例如-XX:SurvivorRatio=8,设置的比例就是8 :1 :1
还可以设置“-Xmn”设置新生代最大内存大小,如果设置同时设置了以"-Xmn"生效,不过"-Xmn"一般不设置,使用默认值即可
对象分配的过程
一般情况:
创建的对象在Eden区开辟空间,当在Eden开辟空间时Eden满,触发YGC(Young GC)/Minor,回收Eden区和From区。然后把幸存的对象还入To区,并且把age加1。清空Eden区和From区,然后把创建的对象放入Eden。这次的From区在下一次作为To区。age满15的对象放入老年代,这个阈值可以通过参数"-XX:MaxTenuringThreshold=<N>"设置。当From满时不会触发YGC/Minor。当From满时会把年龄大的对象直接放入老年代(动态年龄判断的规则)。
特殊情况:
总结:
针对s0、s1,复制之后发生交换、谁空谁是To
关于垃圾回收,频繁在新生代发生,很少在老年代发生、几乎不在永久代/元空间发生