JVM学习(二)Java堆

JVM学习(二)Java堆

前言

侵删,记录自己学习JVM时看书或查找资料看到的要点


JAVA堆为对象创建分配内存的两种方式

​ 假设Java堆中内存是绝对规整的,所有用过的内 存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配 内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称 为“指针碰撞”(Bump the Pointer)。如果Java堆中的内存并不是规整的,已使用的内存和空 闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记 录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。选择哪种分配方式由 Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

JVM在划分空间时要考虑的问题

除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常 频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的, 可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来 分配内存的情况。解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理 ——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分 配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内 存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内 存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。 虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。

JAVA堆:新生代,老年代

Java堆从GC的角度可以细分为新生代老年代,新生代分为Eden区From Survivor区To Survivor区

在这里插入图片描述

Ps:

堆的大小可以通过参数 -Xms(程序启动时占用的内存大小)、-Xmx(JVM最大可用内存)指定。

从图中可以看出,默认空间分配比例为Eden:From:To=8:1:1

新生代

新生代是用来存放新生的对象,一般占据Java堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。

Ps:JVM每次只会使用Eden和其中一块Survivor区域来为对象服务,无论什么时候总有一块Survivor区域是空闲状态,因此新生代实际可用内存空间为总新生代空间的90%

Eden区

Java新对象的出生地,(如果新创建的对象占用内存很大就会直接分配到老年代)当Eden区内存不够时会触发MinorGC,对新生代区进行一次垃圾回收

From Survivor 区

上一次GC的幸存者,作为这一次GC的被扫描者。

To Servivor区

保留了一次MinorGC过程中的幸存者

MinorGC

MinorGC的过程(复制>清空>互换)

MinorGC采用复制算法

  1. eden、from 复制到to,年龄+1

    首先,把Eden和From Servivor区域中存活的对象复制到To Servicor区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 To Servivor 区不够位置了就放到老年区

  2. 清空eden、from

    其次,清空 Eden和From Servivor中的对象

  3. to ,from互换

    最后,To Survivor区和From Servivor区互换,原To Servivor 区成为下一次GC时的From Servivor区

Ps:为什么新生代采用复制算法? 效率高,并且不会像标记-清除算法那样产生大量空间碎片

老年代

主要存放应用程序中生命周期长的内存对象。

​ 老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

MajorGC

MajorGC采用标记-清除算法

首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC耗时较长因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出OOM(Out of Memory)异常。

永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被 放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这 也导致了永久代的区域会随着加载的Class的增多而胀满,终抛出OOM异常。

注意: JAVA8与元数据

在Java8中,永久代已经被移除,被一个称为 “元数据区”(元空间)的区域所取代。元空间 的本质和永久代类似,元空间与永久代之间大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中 (重要),这样可以加载多少类的元数据就不再由 MaxPermSize控制, 而由系统的实际可用空间来控制。

引用 JVM之方法区和永久代(现叫元空间)关系 中的评论留言

小-冯:很多人吧方法区称为永久代,但是两者其实并不等价.永久代只是来实现方法区而已,这样HotSpot可以像管理java的堆内存一样管理这部分的内存,能够省去专门为方法区编写内存代码管理的工作.我觉得读完这段话,应该能明白方法区和永久代的关系.我认为方法区使用的是JVM的堆内存,但是JVM团队想区分开来,所以有重新起名叫做方法区,那么方法区和永久代到底是很么关系,上面的一段话其实已经说明了,方法区和永久代本质上不是等号的,永久代只是为了实现方法区,使得JVM可以通过像管理堆内存一样去实现管理方法区的GC机制

总结

  • java堆为对象创建分配内存有两种方式,一种是**“指针碰撞”,另外一种是“空闲列表”**
  • java堆可划分为新生代和老年代,在堆空间的占比上,新生代:老年代为1:2
  • 新生代采用的MinorGC采用复制算法,老年代的MajorGC采用标记-清除算法。复制算法比标记-清除算法效率高;标记-清除算法会产生内存碎片。由于新生代采用复制算法,所以无论什么时候总有一块Survivor区域是空闲状态,因此新生代实际可用内存空间为总新生代空间的90%
  • 永久代在Java8中已被移除,由元数据区(元空间)取代。元空间使用本地内存,类的元数据放入本地内存中,字符串池和类的静态变量放入java堆中

参考资料

JVM之方法区和永久代(现叫元空间)关系

JVM调优总结 -Xms -Xmx -Xmn -Xss

Java 堆的新生代、老年代及其GC

《Java虚拟机(第二版)》

《JAVA核心面试知识整理.pdf》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值