JVM-栈、堆、GC

重要理论

  1. 栈管运行,堆管存储
  2. 程序 = 算法 + 数据结构
  3. 队列(FIFO)先进先出
  4. 栈(FILO)先进后出
  5. java方法在栈内就是栈帧(就是栈的一个格子)

基本介绍

  1. 栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,生命周期是跟随现成的生命期,线程结束占内存也就释放,对于栈来说不存在垃圾回收问题,是线程私有的。
  2. 8中基本类型的变量+对象的引用变量(对象名)+实例方法都是在函数的栈内存中分配

主要存储内容

栈帧中主要保存3类数据:

  1. 本地变量:输入参数和输出参数以及方法内的变量
  2. 栈操作:记录出栈、入栈的操作
  3. 栈帧数据:包括类文件、方法等

栈运行原理

每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM实现有关,通常在265K~765K,约等于1KB
在这里插入图片描述
其中图例的曲线(父帧->方法索引)父帧存取的内容可理解为PC寄存器存储的内容

java.lang.StackOverflowError

  1. 这是一个错误,是java.lang.Error的子类(异常是Exception的子类)
  2. 栈溢出,常出现在递归中

栈帧存储的内容

  1. 局部变量表
  2. 操作数帧
  3. 指向运行时常量池的引用
  4. 方法返回地址
  5. 动态链接
    在这里插入图片描述

栈、堆、方法区的交互关系

在这里插入图片描述

基本

  1. 一个JVM实例只存在一个堆内存,堆内存的大小可以调节。
  2. 类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行其执行
  3. 堆内存分为三部分:
    • 新生区(Young/New)
    • 养老区(Old/Tenure)
    • 永久区(Perm)

堆结构

在这里插入图片描述

  1. java8之后将永久存储区变为元空间,即7是永久代,8是元空间
  2. 幸存者0 是 From区,幸存者1 是 To区,但是由于MinorGC(垃圾收集)的机制,有时候幸存者0 是To区,幸存者1 是from区,下文解释
  3. 物理上,只有新生区和老年区
  4. 新生区中,伊甸区:From区:To区 = 8 : 1 :1
  5. 堆内存中,新生区 : 老年区 = 1:2

MinorGC机制

在这里插入图片描述

  1. 复制:eden、SurvivorFrom复制到SurvivorTo,年龄+1
    • 首先,当Eden区满的时候会触发第一次GC,把活着的对象拷贝到From
    • 当eden再次触发GC的时候会扫描eden和from区,对这两个区域进行垃圾回收,活着的对象复制到to
    • 把活下来的对象年龄+1,若达到老年标准,则复制到老年区
  2. 清空:清空eden、from中的对象,也就是复制之后谁空谁是To
  3. 交换:To 和 From交换
    • 最后,To 和 From互换,原To成为下一次GC时的From
    • 部分对象会在from 和 to区域复制来复制去,如此交换15次还是存活,就存入老年代
    • 交换次数JVM参数MaxTenuring Threshold决定,默认值15

堆溢出

  1. java.lang.OutOfMemoryError:java heap soace异常

  2. 原因有二:

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

永久代

在这里插入图片描述

  1. 虽然JVM规范将方法区描述为一个堆的逻辑部分,但它还有一个别名叫做Non-Heap(非堆),目的就是和堆分开
  2. 永久代是方法区的一个实现
  3. 永久存储区是一个常驻内存区域,用于存放JDK自身所懈怠的Class,Interface的元数据(比如rt.jar,项目导进去的jar),也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,必须关闭JVM才会释放此区域所占用的内存

永久代和元空间

  1. 永久代是用的是JVM堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存
  2. 默认情况下,元空间的大小仅受本地内存限制
  3. 类的元数据放入native memory,字符串池和类的静态变量放入java堆中
  4. 元空间可以加在多少泪的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制

堆内存调优

调优

堆内存优化简介

-Xms设置初始分配大小,默认为物理内存的“1/64”
-Xmx最大分配内存,默认为物理内存的"1/4"
-XX:+PrintGCDetails输出详细的GC处理日志

查看参数

//Runtime对象,即运行时记录着各种参数的对象
int availableProcessors = Runtime.getRuntime().availableProcessors();//cpu处理器数量
System.out.println(availableProcessors);

//开发时这两个值必须一样:避免内存忽高忽低(GC和程序抢内存),导致程序停顿
long maxMemory = Runtime.getRuntime().maxMemory();//堆内存最大大小
long totalMemory = Runtime.getRuntime().totalMemory();//堆内存默认初始大小
System.out.println("-Xmx:MAX_MEMORY = "+maxMemory+"(字节)、"+(maxMemory/(double)1024/1024)+"MB");
System.out.println("-Xms:TOTAL_MEMORY = "+totalMemory+"(字节)、"+(totalMemory/(double)1024/1024)+"MB");

设置参数

IDEA设置参数

Run -> Exit Configuration -> VM options:

-Xms10m -Xmx10m -XX:+PrintGCDetails

堆溢出

  1. Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

在这里插入图片描述

输出详细GC收集日志信息

普遍规律:[ 名称 : GC前内存占用 -> GC后内存占用 (该区内存总大小) ]

GC

[GC (Allocation Failure) [PSYoungGen: 96K->64K(2560K)] 5198K->5166K(9728K), 0.0003285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  1. [PSYoungGen: 96K->64K(2560K)]:
    GC类型:YoungGC前新生代内存占用 -> YoungGC后内存占用(新生代总大小)
  2. 5198K->5166K(9728K)
    YoungGC前JVM堆内存占用 -> YoungGC后JVM堆占用(JVM堆总大小),YoungGC耗时
  3. 0.0003285 secs
    YoungGC耗时
  4. Times: user=0.00 sys=0.00, real=0.00 secs
    用户耗时,系统耗时,实际耗时

FullGC

[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 4015K->3996K(7168K)] 4015K->3996K(8704K), [Metaspace: 3247K->3247K(1056768K)], 0.0072710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  1. [ParOldGen: 4015K->3996K(7168K)]
    GC类型(Old):GC前Old区内存占用 -> GC后Old区内存占用(Old区总大小)
  2. [Metaspace: 3247K->3247K(1056768K)]
    GC类型(Perm):GC前Perm区内存占用 -> GC后Perm区内存占用(Perm区总大小)

GC4大算法

GC算法总体概述

  1. 普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
  2. 全局GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的),Major GC的速度一般要比Minor GC 慢上10倍以上

4算法

引用计数法

  1. 记录每个变量的被引用数量,为0时GC该对象
  2. 现一般不用该算法,站空间,且循环引用不好处理

复制算法

  1. 年轻代中使用的是MinorGC,这种GC算法采用的是复制算法
  2. 原理
    • 从根集合(GC root)开始,通过Ttacing从From中找到存货对象,拷贝到To中
    • From、To交换身份,下次内存分配从To开始
  3. 优点:不会产生内存碎片
  4. 缺点:耗空间

在这里插入图片描述

标记清除

  1. 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
  2. 原理:
    • 从根节点开始标记遍历所有的GC Roots,先标记出要回收的对象
    • 遍历整个堆,把标记的对象清除
  3. 优点:节省空间
  4. 缺点会产生内存碎片(对象之间内存空间不连续)

标记压缩

  1. 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
  2. 原理
    • 标记,清除:同标记清除
    • 压缩:再次扫描,并往一段滑动存活对象
  3. 优点:节省空间,且不会产生内存碎片
  4. 缺点:需要移动对象的成本,效率较低,耗时多
  5. 标记-清除-压缩:多次标记清除之后再标记压缩

小总结

  1. 内存效率:复制>标记清除>标记整理
  2. 内存整齐度:复制=标记整理>标记清除
  3. 内存利用率:标记清除=标记整理>复制

分代收集算法

  1. 次数上频繁收集Young区,特点是区域相对较小,对象成活率低:复制算法
  2. 次数上较少收集Old区,特点是区域较大,对象成活率高:标记清除或者标记清除与标记整理的混合实现
  3. 基本不动元空间

GC回收器

操作类

  1. 查看默认垃圾收集器
# JVM参数,在控制台查看
java -XX:+PrintCommandLineFlags -version

# 看该回收器有没有运行
jps 
# 得到GC的进程号,执行以下命令,+为使用,-为未使用
jinfo -flag UseParallelGC pid

# 配置通用语句(最后一个是选择垃圾回收器)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX+UserParallelGC
  1. 默认垃圾回收器
  • UseSerialGC >> 串行GC
  • UseParallelGC >> 并行GC
  • UseConcMarkSweepGC >> CMS并发GC
  • UseParNewGC >> 新生代的并行GC
  • UseParallelOldGC >> 老年区的并行GC
  • UserG1GC

垃圾回收器

在这里插入图片描述
在这里插入图片描述

一些参数

DefNewDefault New Generation默认新生代用的什么收集器
TenuredOld老年代
ParNewParallel New Generation在新生代用并行回收
PSYoungGenParallel Scavenge在新生代用并行回收GC
ParOldGenParallel Old Generation在老年代用并行垃圾收集器
CMSconcurrent mark-sweep用并发收集器

server && client

  1. 适用范围:只需要掌握Server模式即可,Client模式基本不会用
  2. 操作系统
  • 32位WiNdow操作系统,不论硬件如何都默认使用Client的JVM模式
  • 32位其他操作系统,2G内存同时有2个cpu以上用Server模式,低于该配置还是Client模式
  • 64位只有server模式

串行收集器:Serial收集器

在这里插入图片描述

  1. 一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程知道它收集结束
  2. 没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器
  3. 开启后会使用:Serial(Young区用) + Serial Old(Old区用)的收集器组合
  4. 表示:新生代、老年代都会使用串行回收收集器,新生代用复制算法,老年代使用标记-整理算法

串行收集器:Serial Old

  1. Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的老年代垃圾收集器
  2. 在server模式下,主要有以下两个用途
  • 在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用(Parallel Scavenge + Serial Old)
  • 作为老年代版中使用CMS收集器的后备垃圾收集方案

并行回收器:ParNew

在这里插入图片描述

  1. 使用多线程进行垃圾回收,在垃圾收集时,会Stop-the-World暂定其他所有的工作线程直到他收集结束
  2. ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器
  3. 常用对应JVM参数:-XX:+UserParNewGC 启用ParNew收集器,只影响新生代的收集,不影响老年代
  4. 开启上述参数后,会使用ParNew(Young区用) + Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法
  5. 但是,PreNew+Tenured这样的搭配(即上述搭配),JDK8已经不再推荐(deprecated)

并行回收GC:Parallel Scavenge(默认)

在这里插入图片描述

  1. 类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器
  2. 串行收集器在新生代和老年代的并行化
  3. 可控制的吞吐量:Thoughput = 运行用户代码时间/(运行用户代码时间+垃圾收集时间),也即比如程序运行100分钟,垃圾收集时间1分钟,吞吐量就是99%,高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不太需要太多交互的任务。
  4. 自适应调节策略也是ParallelScavenge 收集器与ParNew收集器的一个重要区别。(自适应调节策略:虚拟机会根据当前系统的运行手情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量)
  5. 常用JVM参数:-XX: +UserParallelGC或-XX:+UserParallelOldGC(可相互激活),使用Parallel Scanvenge收集器
  6. 开启该参数后,新生代使用复制算法,老年代使用标记-整理算法

在这里插入图片描述

并行GC:Parallel Old(默认)

  1. Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,该收集器在1.6后才有提供
  2. 1.6前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge + Serial Old)
  3. Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略。在JDK1.8之后(Parallel Scavenge + Parallel Old)
  4. JVM常用参数:-XX:+UserParallelOldGC 使用Parallel Old收集器,设置该参数后,新生代Parallel+老年代Parallel Old

并发标记清除GC(CMS)

在这里插入图片描述

  1. CMS收集器是一种以获取最短回收停顿时间为目标的收集器,适合应用在互联网站或者B/S系统的服务器上,这类应用有其中是服务器的响应速度,希望系统停顿时间最短
  2. CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器
  3. 并发收集低停顿,并发指的是与用户线程一起执行
  4. 开启该收集器的JVM参数: -XX:+UserConcMarkSweepGC 开启后会自动将 -XX:+UserParNewGC打开
  5. 开启该参数后,使用ParNew(Young区用)+CMS(Old区用)+Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器

四步过程

  1. 初始标记
    只是标记一下GC Roots能直接关联(直达)的对象,速度很快,仍然需要暂停所有的工作线程
  2. 并发标记,和用户线程一起
    进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象(标记第一步延伸出来的)
  3. 重新标记
    修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。由于并发标记时,用户线程仍然运行,因此正式清理前,再做修正
  4. 并发清除,和用户线程一起
    清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象。由于耗时最长的并发标记和并发清除过程中,垃圾手机线程可以喝用户现在一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行

优缺点

优点:

  • 并发收集停顿低

缺点:

  • 并发执行,对CPU资源压力大:由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,因此,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败,将触发担保机制,串行老年代收集器将会以SWT的方式进行一次GC,造成较大停顿时间
  • 采用标记清除算法会导致大量的内存碎片

如何选择GC回收器

组合的选择

  1. 单CPU或小内存,单机程序

-XX:+UserSerialGC

  1. 多CPU,需要最大吞吐量,如后台计算型应用

-XX:+UserParallelGC 或
-XX:+UserParallelOldGC

  1. 多CPU,追求低停顿时间,需快速响应如互联网应用

-XX:+UserConcMarkSweepGC
-XX:+ParNewGC

图示

在这里插入图片描述

G1收集器(1.9默认)

以前收集器的特点

  1. 年轻代和老年代是鸽子独立且连续的内存块;
  2. 年轻代收集使用单eden+S0+S1进行复制算法;
  3. 老年代收集必须扫描整个老年代区域
  4. 都是以尽可能少而快速的执行GC为设计原则

G1定义

  1. G1是一款面向服务端应用的收集器
  2. 应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求
  3. 主要是改变Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成一个个大小一样的region,每个region从1M到32M不等。一个region可能属于Eden,Survivor或者Tenured内存区域

特征

  1. 像CMS收集器一样,能与应用线程并发执行
  2. 整理空闲空间更快
  3. 需要更多的时间来预测GC停顿时间
  4. 不希望牺牲大量的吞吐性能
  5. 不需要更大的Java Heap

与CMS相比

  1. G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片
  2. G1的Stop The World(SWT)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间

特点

  1. G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW
  2. G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片
  3. 宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region)
  4. G1收集器里面将整个的内存区混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但他们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域
  5. G1虽然也是分带收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换

底层原理

  1. Region区域化垃圾收集器
  • 最大的好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可
  1. 区域化内存划片Region,整体编为了一些列不连续的内存区域 ,避免了全内存区的GC操作
  2. 核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小
  3. 在堆的使用上,G1 并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可,每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n,可指定分区大小(1M~32M,且必须是2的幂),默认将整堆划分为2048个分区
  4. 大小范围在1M~32M,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048 = 64G内存

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

回收步骤

G1下的YoungGC

针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片

  • Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部分晋升到Old区
  • Survivor区的数据移动到新的Survivor区,部分数据晋升到Old区
  • 最后Eden区收拾干净了,GC结束,用户的应用程序继续执行

在这里插入图片描述
在这里插入图片描述

4步过程

  1. 初始标记:只标记GC Roots能直接关联到的对象
  2. 并发标记:进行GC Roots Tracing的过程
  3. 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
  4. 筛选回收:根据时间来进行价值最大化的回收

在这里插入图片描述

常用配置参数

-XX:+UserG1GC
-Xmx32g :最大内存32g
-XX:G1HeapRegionSize=n:设置G1区域的大小。值是2的幂,范围是1M到32M
-XX:MaxGCPauseMillis=n:最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间

三步归纳:开始G1+设置最大内存+设置最大停顿时间

和CMS的比较

  1. G1不会产生内存碎片
  2. 是可以精确控制停顿。该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域

spring boot

  1. IDEA开发完微服务工程
  2. maven进行clean package
  3. 要求微服务启动的时候,同时配置JVM/GC的条有参数
  1. 公式:java -server jvm的各种参数 -jar jar/war包名字

java -server -Xms1024m -Xmx1024m -XX:+UserG1GC -jar A.war

G1的推荐文章

1
2
3
4

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值