C/C++ 内存管理

Java的内存管理

Java中为了简化对象的释放 引入了自动的垃圾回收(Garbage Collection简称GC)机制
通过垃圾回收器来对不再使用的对象完成自动的回收 垃圾回收器主要负责对
上的内存进行回收
其他很多现代的语言比如C# Python Go都拥有自己的垃圾回收器

垃圾回收的对比

垃圾回收应用场景

  1. 解决系统僵死的问题: 大厂的系统出现的许多系统僵死问题 都与频繁的垃圾回收
  2. 性能优化: 对垃圾回收器进行合理的设置可以有效地提升程序的执行性能
  3. 高频面试题
    1. 常见的垃圾回收器
    2. 常见的垃圾回收算法
    3. 四种引用
    4. 项目中用了哪一种垃圾回收器

方法区的回收

线程不共享的部分 都是伴随着线程的创建而创建 线程的销毁而销毁
而方法的栈帧在执行完方法之后就会自动弹出栈并释放掉对应的内存

方法区中能回收的内容主要就是不再使用的类
判定一个类可以被卸载 需要同时满足以下三个条件

  1. 此类所有实例对象都已经被回收 在堆中不存在任何该类的实例对象以及子类对象
  2. 加载该类的类加载器已经被回收
  3. 该类对应的java.lang.Class 对象没有在任何地方被引用

堆回收

Java中对象是否能被回收 是根据对象是否被引用来决定的 如果对象被引用了 说明该对象还在使用 不允许被回收

如果在main方法中最后执行 a1=null b1=null 是否能回收A和B对象?
可以回收 但方法中已经没有办法使用引用去访问A和B对象

如何判断堆上对象是否被引用?
常见的有两种判断方法: 引用计数法 和 可达性分析

引用计数法和可达性分析法

引用计数法

引用计数法会为每一个对象维护一个引用计数器 当对象被引用时+1 取消引用时-1

优缺点:

可达性分析法

GC Root对象

  1. 线程Thread对象: 引用线程栈帧中的方法参数 局部参数等
  2. 系统类加载器加载的java.lang.Class对象 , 引用类中的静态变量
  3. 监视器对象 用来保存同步锁synchronized关键字持有的对象 (即同步锁synchronized关键字所持有的对象是不可以被回收的 )
  4. 本地方法调用时使用的全局对象

五种对象引用

可达性算法中描述的对象引用 一般指的是强引用 即使GCRoot对象和普通对象有引用关系 只要这层关系存在 普通对象就不会被回收

除了强引用外 Java中还设计了 软引用 弱引用 虚引用 终结器引用

软引用

当GC Root对象与SoftReference对象内的对象A 之间强引用关系删除后 当程序内存不足时 就会将软引用中的数据回收
GCRoot 和 SoftReference对象之间应该是强引用关系 否则软引用对象也可能会被回收

实现案例

弱引用

弱引用的整体机制和软引用基本一致 区别在于弱引用包含的对象在垃圾回收时 不管内存够不够都会直接被回收
开发中基本不用

虚引用和终结器引用

这两种引用在常规开发中时不会使用的

垃圾回收算法

核心算法:
简单来说i 垃圾回收要做的有两件事:

  1. 找到内存中存活的对象
  2. 释放不再存活对象的内存 使得程序能再次利用这部分空间

垃圾回收算法评价标准

STW:

评价标准:

  1. 吞吐量

  2. 最大暂停时间

  3. 堆使用效率

垃圾回收算法

标记清除算法
复制算法
标记整理算法
分代GC

分代GC

分代垃圾回收将整个内存区域分为年轻代和老年代

分代回收时 创建出来的对象 首先会被放入Eden伊甸园区
随着对象在Eden区越来越多 如果Eden区满 新创建的对象依已经无法放入 就会触发年轻代的GC 称为Minor GC或者 Young GC
Minor GC会把需要Eden中和From需要回收的对象回收 把没有回收的对象放入To区

接下来 S0会变成To区 S1变成From区 当Eden区满时再往里放入对象 依然会发生Minor GC
此时会回收Eden区和S1(from)中的对象 并把Eden和From区中剩余的对象放入S0
注意:每次Minor GC中都会为对象记录他的年龄 初始值为0 每次GC完加1

如果Minor GC后对象的年龄到达阈值(最大15 默认值和垃圾回收器有关) 对象就会晋升至老年代

当老年代中空间不足 无法放入新的对象时
先尝试Minor GC如果还是不足(先尝试Minor GC 是因为当年轻代满时 即使一些对象没有到达最大阈值15 也会被放入老年代)
就会触发Full GC m, FUll GC会对整个堆(年轻代 + 老年代)进行垃圾回收 -> Full GC stw长 (长于Minor 的stw)
如果Full GC依然无法回收掉老年代的对象 那么当对象继续放入老年代时 就会抛出Out of Memory异常

问题: 为什么分代GC算法要把堆分成年轻代和老年代

分代GC算法将堆分成年轻代和老年代主要原因有:

  1. 可以通过调整年轻代和老年代的比例来适应不同类型的应用程序 提高的内存的利用率和性能
  2. 新生代和老年代使用不同的垃圾回收算法 新生代一般选择复制算法 老年代可以选择标记-清除和标记整理算法 由程序员来选择灵活度高的
  3. 分代的设计中允许只回收新生代(minor gc) 如果能满足对象分配的要求就不需要堆整个堆进行回收(full gc) stw的时间就会减少

垃圾回收器

垃圾回收器的组合关系:

年轻代-Serial垃圾回收器

老年代-SerialOld垃圾回收器

年轻代-ParNew垃圾回收器(JDK9 以后不建议使用)

老年代-CMS 垃圾回收器

执行步骤

缺点:

CMS垃圾回收器关注的是系统的暂停时间 允许用户线程和垃圾回收线程在某些步骤中同时执行
减少了用户线程的等待时间