栈是运行时的单位,而堆是存储的单位。
栈是解决程序运行的问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即书怎么放,放在哪。
堆内存划分:
对于大多应用,java堆是java虚拟机管理的内存中最大的一块,被所有线程共享。此内存区域唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存。
为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(这样划分的目的使得JVM能够更好的管理堆内存中的对象,包括内存的分配以及回收):
- 新生代(Young):新对象和没达到一定年龄的对象都在年轻代。新生代又分为一个Eden取和两个Survivor区(From Survivor、To Survivor);
- 老年代(Old Memory):主要存放应用程序中生命周期长的存活对象;
- 元空间(JDK1.8前叫永久代、持久代):用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。JDK1.8之前是占用JVM内存,JDK1.8之后使用的是物理内存。
堆的内存模型如下:
从图中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
默认的,Edem : from : to = 8 :1 : 1 ( 可以通过参数–XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
对象的分配过程:
所有新生成的对象首先都是放在年轻代中的,年轻代的目标就是尽可能快速的收集那些生命周期短的对象
- 开始分配对象时,保存在Eden区;
- Eden区满时,触发GC(Minor GC),将Eden区存活对象复制到一个Survivor0区,然后清空Eden区;
- 当这个Survivor0区也存放满的时候,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区;
- 此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复
- 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。每次执行minor GC 后,都会将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
- 若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝。
GC堆
Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、FullGC ( 或称为 Major GC )。
Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。新生代是 GC 收集垃圾的频繁区域。
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
现实的生活中,老年代的人通常会比新生代的人"早死"。堆内存中的老年代(Old)不同于这个,老年代里面的对象
几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 "死掉" 了的。因此,Full GC 发生的次数不
会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。
另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象
分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。