欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

【JVM学习】垃圾收集器与内存分配策略

程序员文章站 2022-03-26 17:17:30
垃圾收集器与内存分配策略对象已死?(判断对象是否可以回收)引用reference类型的数据中存储的数值代表的是另一块内存的起始地址强引用:代码中的引用赋值 例如 new对象;意义:对象不会被回收软引用:还有用非必须的对象;意义:在系统将要发生内存溢出前回收弱引用:非必须对象;意义:每次垃圾回收时都会被清理虚引用:最弱的一种引用;意义:可以让一个对象被回收时收到一个系统通知引用计数算法在对象中添加一个引用计数器,被引用加一,引用失效减一,为零就不会再使用可以回收优点:原理简单...

垃圾收集器与内存分配策略

对象已死?(判断对象是否可以回收)

引用

  • reference类型的数据中存储的数值代表的是另一块内存的起始地址

    • 强引用:代码中的引用赋值 例如 new对象;意义:对象不会被回收
    • 软引用:还有用非必须的对象;意义:在系统将要发生内存溢出前回收
    • 弱引用:非必须对象;意义:每次垃圾回收时都会被清理
    • 虚引用:最弱的一种引用;意义:可以让一个对象被回收时收到一个系统通知

引用计数算法

  • 在对象中添加一个引用计数器,被引用加一,引用失效减一,为零就不会再使用可以回收
  • 优点:原理简单,效率高
  • 缺点:实用性不强,例如 很难解决对象间的互相循环引用的问题

可达性分析算法

  • 判断某个对象到GC Roots间有没有引用链相连,如果没有就说明对象不可达、不再使用可以回收

  • GC Roots

    • 虚拟机栈的本地变量表中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(Native方法)引用的对象
    • java虚拟机内部的引用,如基本数据类型对应的Class对象,常驻的异常对象,类加载器
    • 同步锁持有的对象
    • 反应虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

生存还是死亡(判断对象是否真的能被回收)

  • 至少要经历两次标记(判断),如果第一次被标记成不可达,但是在第二次标记前又与GC Roots建立了链接就不会被清理

回收方法区

  • 《java虚拟机规范》不强制方法区实现垃圾收集
  • 方法区垃圾收集性价比比较低
  • 主要回收废弃的常量和不再使用的类型(类型指加载到内存中的Class)

垃圾收集算法

引用计数式垃圾收集(直接垃圾收集)

追踪式垃圾收集(间接垃圾收集)(主流)

  • 分代收集理论

    设计原则:收集器应该将java堆划分出不同区域,然后将回收对象依据其年龄分配到不同的区域之中存储

    • 弱分代假说:绝大多数对象都是朝生夕死的

    • 强分代假说:熬过越多次垃圾收集过程的对象越你难以死亡

    • 分代

      • 新生代
      • 老年代
    • 跨分代引用假说:存在相互引用关系的对象应该倾向于同生同死,跨代引用占比较少。

      • 存在问题:当扫描新生代对象时如果有跨代引用还需要扫描整个老年代,成本较高

        • 解决办法:新生代建立一个全局数据结构(记忆集),这个结构将老年代划分成若干小块,标识出老年代的哪一块内存存在跨代引用。Minor GC时只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描,而不用扫描整个老年代
  • 标记-清除算法:标记后清除被标记的对象

    • 缺点:1、执行效率不稳定(受要标记的目标对象数量的限制);2、内存空间碎片化(产生不连续的内存碎片)
  • 标记-复制算法:将内存划分为大小相等的两块,每次只用其中一块,当内存用完了就将存活对象复制到另一块,然后将已使用的内存一次清理掉

    • 缺点:空间浪费太严重
    • 新生代优先采用此算法,而老年代不会使用这种方法,因为新生代98%对象都是朝生夕死的,这种情况实现简单、效率高
  • 标记-整理算法:先标记,让存活对象都移动到内存一端,然后清理掉边界以外的内存

经典垃圾收集器

Serial收集器

  • 单线程收集器
  • 优点:简单高效、内存消耗最小
  • 缺点:工作时必须“Stop The World”,用户体验差

ParNew收集器

  • Serial收集器多线程并行版本
  • HotSpot虚拟机中第一款退出历史舞台的垃圾收集器,只能和CMS配合使用而CMS将会替代CMS

Parallel Scavenge收集器

  • 多线程,基于标记-复制算法
  • 设计目标:达到一个可控的吞吐量(也就是控制垃圾回收所占用的时间)

Serial Old收集器

  • Serial的老年代版本
  • 单线程,基于标记整理算法

Parallel Old收集器

  • Parallel Scavenge收集器老年代版本
  • 多线程,基于标记-整理算法

CMS收集器

  • 目标:为了获取最短回收停顿时间

  • 多线程,基于标记-清除算法

  • 缺点:1、处理器资源敏感,会占用一部分资源导致程序变慢,总吞吐量降低。2、无法处理浮动垃圾,可能导致full gc发生。3、无法处理空间碎片,只能依靠full gc

    浮动垃圾:CMS标记和清理阶段用户线程会同时运行并产生新的垃圾,因为有些垃圾在标记后产生所以本次无法清理,只能等到下次。

Garbage First收集器(G1)

  • 将堆内存划分为多个大小相等的独立区域(Region),每个区域都可以根据需要扮演新生代的Eden空间、Survivor空间或老年代空间,每次回收时G1会判断那块回收性价比最高,收益最大;Region中设有Humongous区域,用来存储大对象(超过Region一半的对象),而超级大对象会被存放在多个连续的Humongous区域

  • 与CMS比较

    • 优势:1、不会产生垃圾碎片;2、创新性设计带来较多红利:可以指定最大停顿时间,分区域的内存布局,按收益动态确定回收集
    • 弱势:1、占用内存多、负载率高

基础故障处理工具

jps:虚拟机进程状况工具,主要用来查看进行的唯一ID

jstat: 虚拟机统计信息监视工具,主要用于查看虚拟机各种运行状态信息

jinfo:java配置信息工具,主要使用-sysprops 打印System.getProperties()的内容

jmap:java内存映像工具,主要用于生成堆转储快照

jhat:虚拟机堆转储快照分析工具,图形化工具更适合这个工作 所以这个命令用处不大

jstack:java堆栈跟踪工具,主要用于生成虚拟机当前时刻线程快照,例如可以查看没有相应的线程在后台干什么

内存回收策略

在经典分代设计下,新生对象通常会分配在新生代中,少数情况下也可能直接分配在老年代

对象优先在新生代Eden区域分配

大对象直接进入老年代

长期存活对象进入老年代

  • Eden中的对象经过一次GC仍然存活并且能被Survivor容纳就会被转移到Survivor中,并将对象年龄设置为1,对象在Survivor中每熬过一次GC就增加一岁,年龄增加到15(默认 可以设置)时进入老年代

动态对象年龄判定

  • HotSpot虚拟机并不是永远等到对象年龄达到标准才晋升到老年代,如果Survivor空间中的相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就直接进入老年代

选择合适的垃圾收集器

垃圾收集器工作内容

  • 垃圾收集、堆内存的管理与布局、对象的分配、与解释器的协作、与编译器的协作,与监控子系统的协作等等

应用只要运行数分钟甚至数秒,只要虚拟机能正确分配内存,在堆耗尽之前就会退出

  • Epslion收集器,不进行垃圾回收,但负载极小

收集器的权衡

  • 应用程序的关注点

    • 吞吐量
    • 延迟
    • 内存占用

虚拟机及垃圾收集器日志

  • 1、查看GC基本信息:jdk9以前 -XX:+PrintGC,9以后 -Xlog:gc
  • 2、查看GC详细信息:9以前 -XX:+PrintGCDetails,9以后 -X-log:gc*
  • 3、查看GC前后堆、方法区可用容量变化:9以前-XX:+PrintHeapAtGC,9以后 -Xlog:gc+heap=debug
  • 4、查看GC过程中用户线程并发时间以及停顿的时间:9以前-XX:Print-GCApplicationConcurrentTime 及 -XX:+PrintGCApplicatiionStoppedTime,9以后 -Xlog:safepoint
  • 只列出一些常用的命令

本文地址:https://blog.csdn.net/qq_43005544/article/details/110282695

相关标签: java