【JVM】jvm虚拟机中的堆

一、JVM体系结构

在这里插入图片描述我们接下来说的JVM中运行时数据区中的堆区(Heap Area)。

二、Java堆简介

对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块区域,在虚拟机启动时创建。此内存区域的唯一的目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。在《Java虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配”。

2.1 堆的特点

  1. 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域
  2. Java堆区在JVM启动的时候被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。但是堆内存的大小是可以调节的
  3. 《Java虚拟机规范》规定,堆可以处于 **物理上不连续** 的内存空间中,但是 **逻辑上** 它应该是被视为 连续的
  4. 堆针对一个 JVM 进程 来说是唯一的,也就是一个进程只有一个JVM,但是进程包含多个线程,他们是共享同一堆空间的
  5. 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区**Thread Local Allocation Buffer, TLAB**

2.2 堆空间细分

在这里插入图片描述从上图中我们可以知道,堆的空间可以被细分为很多区:

  1. 年轻代(Young Gen)
  2. 老年代(Old Gen)
  3. 永久代(Perm Gen)

其中年轻代被分为 Eden 空间Survivor 1空间From区)、Survivor 0空间To 区),备注:谁空谁就是 To区

  • 所有对象一出生都会在Eden区,并且对象年龄为0。Eden区满了之后会触发minor GC, minor GC只回收年轻代中需要回收的对象。每一次minor GC,若该对象没有被回收,那么对象的年龄就会 + 1。对象到达阈值以后该对象会进入老年代(android阈值为6)。
  • 当老年代空间满了以后会触发full GC,full GC会同时回收年轻代和老年代中所有需要回收的对象。

2.3 堆空间的分代思想

年轻代、老年代、永久代

  • 年轻代用于放置临时对象或者生命周期短的对象
  • 老年代用于放置生命周期长的对象
  • 永久代或者元空间,用于存放常量
  • minor GC (轻GC)只管年轻代,Full GC(重GC) 同时管理年轻代和老年代

为什么要分代?有什么好处?

  • 经研究表明,不同对象的生命周期是不一致的,但是在具体的使用过程中,70%–90%的对象都是临时对象
  • 分代唯一的理由就是优化GC的性能,如果没有分代,那么所有对象都处于一块空间,GC想要回收,则GC需要描述所有的对象,分代之后,长期持有的对象可以被挑出,短期持有的对象可以固定在一个位置回收,省掉很大一部分空间利用。

2.4 堆的默认大小

  • 初始大小:电脑物理内存的 1/64
  • 最大内存大小:电脑物理内存的 1/4

Runtime类:该类对应的JVM运行时数据区

long initialMemory = Runtime.getRuntime().totalMemory();
long maxMemory = Runtime.getRuntime().maxMemory();
long freeMemory = Runtime.getRuntime().freeMemory();

三、JVM堆内存常用参数

参数 说明
-Xms 堆内存初始大小,单位m(兆)、g(G)
-Xmx(MaxHeapSize) 堆内存最大允许大小,一般不要大于物理内存的80%
-XX:PermSize 非堆内存初始大小,一般应用设置初始化200m,最大1024m就够了
-XX:MaxPermSize 非堆内存最大允许大小
-XX:NewSize(-Xns) 年轻代内存初始大小
-XX:MaxNewSize(-Xmn) 年轻代内存最大允许大小,也可以缩写
-XX:SurvivorRatio=8 年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1
-Xss 堆栈内存大小

四、垃圾回收算法(GC,Garbage Collection)

以下几种垃圾回收算法只是抛砖引玉,具体详细的算法说明还需要自行搜索并加以学习,算法还有很多,所以学习的东西有很多哦。

红色是标记的非活动对象,绿色是活动对象。

4.1 标记-清除(Mark-Sweep)

GC分为两个阶段,标记和清除。首先标记所有可回收的对象,在标记完成后统一回收所有被标记的对象。同时会产生不连续的内存碎片。碎片过多会导致以后程序运行时需要分配较大对象时,无法找到足够的连续内存,而不得已再次触发GC。
在这里插入图片描述

4.2 复制(Copy)

将内存按容量划分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。这样使得每次都是对半个内存区回收,也不用考虑内存碎片问题,简单高效。缺点需要两倍的内存空间。
在这里插入图片描述

4.3 标记-整理(Mark-Compact)

也分为两个阶段,首先标记可回收的对象,再将存活的对象都向一端移动,然后清理掉边界以外的内存。此方法避免标记-清除算法的碎片问题,同时也避免了复制算法的空间问题。

一般年轻代中执行GC后,会有少量的对象存活,就会选用复制算法,只要付出少量的存活对象复制成本就可以完成收集。

而老年代中因为对象存活率高,没有额外过多内存空间分配,就需要使用标记-清理或者标记-整理算法来进行回收。
在这里插入图片描述

五、常见错误

5.1 为什么会堆内存溢出?

在年轻代中经过GC后还存活的对象会被复制到老年代中。当老年代空间不足时,JVM会对老年代进行完全的垃圾回收(Full GC)。如果GC后,还是无法存放从Survivor区复制过来的对象,就会出现OOM(Out of Memory)。
OOM(Out Of Memory)异常常见有以下几个原因:

  1. 老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace
  2. 永久代内存不足:java.lang.OutOfMemoryError:PermGenspace
  3. 代码bug,占用内存无法及时回收

OOM在这几个内存区都有可能出现,实际遇到OOM时,能根据异常信息定位到哪个区的内存溢出。

可以通过添加个参数 -XX:+HeapDumpOnOutMemoryError,让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后分析。

熟悉了JAVA内存管理机制及配置参数,下面是对JAVA应用启动选项调优配置:

JAVA_OPTS=“-server -Xms512m -Xmx2g -XX:+UseG1GC -XX:SurvivorRatio=6 -XX:MaxGCPauseMillis=400 -XX:G1ReservePercent=15 -XX:ParallelGCThreads=4 -XX:
ConcGCThreads=1 -XX:InitiatingHeapOccupancyPercent=40 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:…/logs/gc.log”

设置堆内存最小和最大值,最大值参考历史利用率设置

设置GC垃圾收集器为G1

启用GC日志,方便后期分析

完结!

猜你喜欢

转载自blog.csdn.net/weixin_44299027/article/details/128401494
今日推荐