JVM堆内存
堆内存的特点
- Java堆(Java Heap)是java虚拟机所管理的内存中最大的一块;
- java堆被所有线程共享的一块内存区域;
- 虚拟机启动时创建java堆;
- java堆的唯一目的就是存放对象实例;
- java堆是垃圾收集器管理的主要区域。
- 从内存回收的角度来看, 由于现在收集器基本都采用分代收集算法, 所以Java堆可以细分为:新生代(Young)和老年代(Old)。 新生代又被划分为三个区域Eden、From Survivor, To Survivor等。无论怎么划分,最终存储的都是实例对象, 进一步划分的目的是为了更好的回收内存, 或者更快的分配内存。
- java堆的大小是可扩展的, 通过**-Xmx和-Xms**控制。
- 如果堆内存不够分配实例对象, 并且对也无法在扩展时, 将会抛出outOfMemoryError异常。
堆内存结构
老年代的变迁 别名(非堆)
- jdk1.6 之前: 永久代,常量池在方法区;
- jdk1.7 :永久代慢慢退化,去永久代,常量池在堆中;
- jdk1.8 之后:取消永久代,改名为元空间,常量池在元空间中。 元空间使用的是计算机的物理内存 不是JVM内存
**新生代:**Young ,主要用来存放新生的对象。幸存者0区和1区,被轻GC回收后剩下的对象保存区域;
老年代:Old Generation,主要存放应用程序声明周期长的内存对象,被重GC回收后仍旧存在的对象会进入老年代;
永久代:另一个别名为“非堆Non-Heap”,jdk1.8 之后取消永久代,改名为元空间。 元空间使用的是计算机的物理内存 不是JVM内存。主要存放JDK自身携带的Class对象和Interface元数据,存储的是java运行时的一些环境或类信息。这个区域不存在垃圾回收!关闭JVM虚拟机就会释放这个区域的内存。
如果一个启动类加载了大量的第三方jar包或者Tomcat部署了太多的应用,大量动态生成的反射类,这些有可能造成永久代空间溢出,抛出outOfMemoryError异常。
堆设置
-Xms :初始堆大小
-Xmx :最大堆大小
-XX:+PrintGCDetails 查看GC回收详情
堆的垃圾回收方式
堆是GC垃圾回收的主要区域。
GC分为两种: Minor GC、Full GC(也叫做Major GC).
Minor GC(简称GC)
Minor GC是发生在新生代中的垃圾收集动作, 所采用的是复制算法。
新生代(Young)几乎是所有java对象出生的地方。即java对象申请的内存以及存放都是在这个地方。java中的大部分对象通常不会长久的存活,因此需要垃圾回收。
新生代是收集垃圾的频繁区域。
回收过程如下:
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳(上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
Full GC
Full GC 基本都是整个堆空间及持久代发生了垃圾回收,所采用的是标记-清除算法。
老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长,一般是Minor GC的 10倍以上。
另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作
实例分析
比如下面的例子,首先使用GC对新生代的内存进行垃圾回收,在回收几次后,未死亡的对象进入老年代,当老年代内存满了之后,使用Full GC对整个堆内存进行垃圾回收,当新生代和老年代的内存都满了之后,会抛出outOfMemoryError异常,