JVM系列(3):堆(Heap)

Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

 

堆区组成

  一.Young Generation Space :新生区 Young/New

  二.Tenure generation space :养老区 Old/ Tenure

  三.Permanent Space :永久区 Perm

  一个 JVM 实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行

  堆在逻辑上可以分为三个部分:新生区,养老区,永久区(1.8后叫元空间)

  物理上分为:新生区,养老区

JVM系列(3):堆(Heap)

新生区 Young/New

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:

  伊甸区(Eden space):所有的类都是在伊甸区被 new 出来的。
  幸存者区(Survivor pace):幸存区有两个: 0 区(Survivor 0 space)和 1 区(Survivor 1 space),又叫from 和 to
  当伊甸园的空间用完时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC,轻GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 0区。若幸存 0 区也满了,再对该区进行垃圾回收,然后移动到 1区。那如果 1 区也满了呢?再移动到养老区

养老区  Old/ Tenure

当对象在新生区经历过多次(默认 15 次)GC 依然幸存则进入养老区。若养老区也满了,那么这个时候将产生MajorGC(FullGC,重GC),进行养老区的内存清理。若养老区执行了 Full GC 之后发现依然无法进行对象的保存,就会产生OOM 异常“OutOfMemoryError”

如果出现 java.lang.OutOfMemoryError: Java heap space 异常,说明 Java 虚拟机的堆内存不够。

原因有二:
  (1)Java 虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx 来调整。
  (2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

永久区  Perm 

永久存储区是一个常驻内存区域,用于存放 JDK 自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。如果出现 java.lang.OutOfMemoryError: PermGen space,说明是 Java 虚拟机对永久代 Perm 内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方 jar 包。

  例如:在一Tomcat 下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致 Perm 区被占满。

Jdk1.6 及之前: 有永久代, 常量池 1.6 在方法区

Jdk1.7:有永久代,但已经逐步“去永久代”,常量池 1.7 在堆

Jdk1.8 及之后: 无永久代,常量池 1.8 在元空间

  实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然 JVM 规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做 Non-Heap(非堆),目的就是要和堆分开。

  对于 HotSpot 虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口 interface)的一个实现,jdk1.7 的版本中,已经将原本放在永久代的字符串常量池移走。常量池(Constant Pool)是方法区的一部分,Class 文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgzwsz.html