【培训】DAY13(上)堆内存

版权声明:本文内容来源于网络,如有侵权请联系删除 https://blog.csdn.net/ZyhMemory/article/details/88895086

定义

Java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,被所有线程共享的一块内存区域,在虚拟机启动时创建。Java堆唯一目的就是存放对象实例。所有的对象实例及数组都要在Java堆上分配内存空间。

特点

  1. 由关键字new产生的所有对象都存储于Java堆(Java Heap)
  2. 实例变量(非static修饰的成员变量)和对象关联在一起,所以实例变量也在堆中
  3. java数组也在堆中开辟内存空间
  4. java堆的大小是可扩展的, 通过-Xmx和-Xms控制。
  5. 如果堆内存不够分配实例对象, 并且对也无法在扩展时, 将会抛出outOfMemoryError(内存溢出)异常。

堆内存划分

在 Java 中,堆被划分成两个不同的区域:

  • 新生代 ( Young )
    主要是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。
  • 老年代 ( Old )
    老年代的对象比较稳定,所以MajorGC不会频繁执行。

新生代 ( Young ) 又被划分为三个区域:
默认的比例,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

  • Eden
    Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
  • From Survivor
    上一次GC的幸存者,作为这一次GC的被扫描者。
  • To Survivor
    保留了一次MinorGC过程中的幸存者。

堆大小 = 新生代 + 老年代。
新生代 ( Young ) 与老年代 ( Old ) 的默认比例值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:
新生代 ( Young ) = 1/3 的堆空间大小。
老年代 ( Old ) = 2/3 的堆空间大小。

当JVM无法为新建对象分配内存空间的时候(Eden满了),Minor GC被触发。因此新生代空间占用率越高,Minor GC越频繁。
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

堆的GC(垃圾回收)

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:

  • Minor GC
  • Full GC ( 或称为 Major GC )
Minor GC

Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
新生代(Young)几乎是所有java对象出生的地方。即java对象申请的内存以及存放都是在这个地方。java中的大部分对象通常不会长久的存活, 具有朝生夕死的特点。
当一个对象被判定为“死亡”的时候, GC就有责任来回收掉这部分对象的内存空间。
新生代是收集垃圾的频繁区域。

过程
  • 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。
  • 紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。
  • 年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。
  • 经过这次GC后,Eden区和From区已经被清空。
  • 这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。
  • 不管怎样,都会保证名为To的Survivor区域是空的。
  • Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
Full GC ( 或称为 Major GC )

Full GC 基本都是整个堆空间及持久代发生了垃圾回收,所采用的是标记-清除算法
标记清除算法从名称上看,此算法可以分为两个阶段:

  • 标记阶段
    在此阶段,垃圾回收器会从mutator(应用程序)根对象开始遍历。
    每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
    在这里插入图片描述
  • 清除阶段
    在此阶段中,垃圾回收器,会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。
    在这里插入图片描述

在标记阶段,从跟对想1可以访问到B,从B又可以访问到E,那么B和E都是可到达对象,同样的道理,F、G、J和K都是可到达对象。在回收阶段,所有未标记为可到达的对象都会被垃圾回收器回收。
垃圾收集后有可能会造成大量的内存碎片,像上面的图片所示,垃圾收集后内存中存在三个内存碎片,假设一个方格代表1个单位的内存,如果有一个对象需要占用3个内存单位的话,那么就会导致Mutator一直处于暂停状态,而Collector一直在尝试进行垃圾收集,直到Out of Memory。

标记/整理算法

标记/整理算法与标记/清除算法非常相似,它也是分为两个阶段:标记和整理

标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段
在这里插入图片描述
可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

不难看出,标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价

不过任何算法都会有其缺点,标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

JVM参数选项

常用易掌握的配置选项

配置选项 说明
-Xms 初始堆大小。如:-Xms256m
-Xmx 最大堆大小。如:-Xmx512m
-Xmn 新生代大小。通常为Xmx的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor ,即 90%
-Xss JDK1.5 + 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话,1M是绝对够用的了
-XX:NewRatio 新生代与老年代的比例,如 -XX:NewRatio = 2,则新生代占整个堆空间的 1/3,老年代占 2/3
-XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8,即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10
-XX:PermSize 永久代(方法区)的初始大小
-XX:MaxPermSize 永久代(方法区)的最大值

猜你喜欢

转载自blog.csdn.net/ZyhMemory/article/details/88895086