(四)内存优化前奏篇(Java虚拟机、垃圾回收机制、内存泄漏/溢出/抖动)

子曰:温故而知新,可以为师矣。 《论语》-- 孔子


作为性能优化专栏的第四篇,也就是内存优化的前奏篇,这一章略微讲解一下 Java虚拟机垃圾回收机制以及内存抖动

一、Java 虚拟机

1. 概念

  • Java 虚拟机是一台 “抽象的计算机”,它拥有自己的处理器,堆栈,寄存器以及相应的指令系统;Java 虚拟机屏蔽了与具体操作系统相关的平台信息,使得 Java 程序只需要生成在该虚拟机上运行的目标代码,就可以在多个平台运行;虽然叫 Java 虚拟机,但在它之上运行的语言不仅有 JavakotlinGroovyScala 等都可以运行。

2. 执行流程

下面这一张图显示了 Java 虚拟机的 执行流程
Java虚拟机执行流程


3. 运行时数据区

下面这一张图展示了 Java 虚拟机 运行时的数据区
Java 虚拟机运行时的数据区

1. 方法区:
  • JVM 用来存储 加载类信息常量静态变量编译后的代码 等数据。
2. 堆内存:
  • JVM 启动时创建,存放对象的实例。垃圾回收期主要就是管理堆内存。如果满了,就会出现 OutOfMemroyError 异常。
3. 虚拟机栈:
  • 每个线程都在这个空间有一个私有的空间。
  • 线程栈由多个栈帧组成。
  • 一个线程会执行一个或多个方法,一个方法对应一个栈帧。
  • 栈帧内容包含:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。
  • 栈内存默认最大是 1M,超出则抛出 StackOverflowError 异常。
4. 本地方法栈:
  • 为虚拟机使用 Native 本地方法而准备的。
5. 程序计数器:
  • 记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行 Native 方法,则计数器值为空。
  • 每个线程都在这个空间中有一个私有的空间,占用内存空间很少。
  • CPU 同一时间,只会执行一条线程中的指令。JVM 多线程会轮流切换并分配 CPU 执行时间的方式。为了线程切换后,需要通过程序计数器,来回复正确的执行位置。


二、四大引用

1. 强引用:

  • 当新建的对象为强引用时,垃圾回收期绝对不会回收它。宁愿抛出 OutOfMemoryError 异常,让程序终止也不会回收。一般 new 出来的对象都是强引用。

2. 软引用:

  • 当新建的对象为软引用时,当内存不够时,回收器就会回收这些对象,如果回收后还是没有足够的内存,抛出 OutOfMemoryError 异常。软引用是用 SoftReference 表示。
SoftReference<Object> obj = new SoftReference<>(object)

3. 弱引用:

  • 当新建的对象为弱引用时,垃圾回收器不管当前内存是否足够,都会回收它的内存。
  WeakReference<Object> weakReference = new WeakReference<Object>(object);  

4. 虚引用:

  • 虚引用跟其他引用都不同,如果一个对象仅持有虚引用,在任何时候都可能被 GC 回收,只是它被回收时会收到一个系统通知。
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();  
PhantomReference<Object> phantomReference = new PhantomReference<>(object,referenceQueue);  
       


三、垃圾回收机制

1. 垃圾标记算法(引用计数算法)

  • 每一个对象都有一个引用计数器,当对象每被引用一次是就 加1,应用失效时就 减1。当计数为 0 时,则将该对象设置为可回收的 “垃圾对象”。
  • 缺点:无法解决对象之间相互引用问题。

2. 垃圾标记算法(根搜索算法)

我用一张图来说明一下:
根搜索算法
选定一写对象作为 GC Root,组成 GC Root Set 集合。以这些对象为目标,向下搜索,如图左边就是不可回收的对象,因为一直到 Ojbect4 都有引用,右边就是可回收对象。举个栗子,我们写的线程就可以作为一个 GC Root,一旦线程结束,里面的方法,对象,变量也都销毁了,都会被垃圾回收器回收。


3. 垃圾收集算法(标记清除算法)

下面用一张图来说明一下:
标记清除算法
顾名思义,比如用垃圾标记算法的根算法标记的对象,那么之后被标记的对象就可以被此标记清除算法回收。此算法有 缺点:垃圾回收时,要先停止 Java 虚拟机工作,然后遍历整个堆内存,非常耗时,而且会产生内存碎片(A 对象开辟了1kb的内存,后来被回收了,再创建一个 B 对象,地址执行之前被回收的地址,但是只用了0.5kb,那么就会有剩余内存空间,如果还想要创建 C 对象的话,空余的内存空间也用不了,因为此地址被 B 对象占用着,这就造成了内存碎片)。


4. 垃圾收集算法(复制算法)

下面用一张图来说明一下:

复制算法
先把内存一分为二,每次只使用其中一个区域,垃圾收集时,将存活的对象全部拷贝到另外一个区域,然后对之前的区域进行全部回收,此算法有 缺点:内存被一分为二,每次可使用的内存就少了。


5. 垃圾收集算法(标记压缩算法)

下面用一张图来说明一下:
标记压缩算法

在标记可回收对象后,将所有的存活对象压缩到内存的一端,让它们排到一起,然后对边界以外的内存进行回收。


6. 垃圾收集算法(分带收集算法)

下面用一张图来说明一下:
分带收集算法
Java 中存在的对象生命周期有较大差别,大部分生命周期很短,有的很长,甚至于应用程序或者 Java 虚拟机生命周期一样。因为分带算法就是根据对象的生命周期长短放到不用的区域。

如右图所示,最开始创建的对象都在第一个区域 Eden,当 Eden 区域中内存不够了,触发GC 操作,就会把 Eden 区域 中存活的对象放入 From survior 区域,清空
Eden 区域,然后接着创建对象,放入 Eden 区域 ,当 Eden 区域 再次内存满了,触发GC 操作,就会把 Eden 区域 中存活的对象 以及 rom survior 区域中存活的对象放入 To survior 区域,同时清空 Eden 区域 以及 From survior 区域,然后再创建对象放入 Eden 区域 ,当 Eden 区域 再次内存满了,触发 GC 操作,就会把 Eden 区域 中存活的对象 以及 To survior 区域 放入到 From survior 区域中,同时清空 Eden 区域 和 To survior 区域 中的对象,就这样周而复始。



四、内存泄漏/溢出/抖动

1. 内存泄漏

  • 一个不再被程序使用的对象或者变量依旧存活在内存中无法被回收。

2. 内存溢出

  • 当程序申请内存时,没有足够的内存供程序使用。比较小的内存泄漏并不会有太大的影响,但内存泄漏多了,占用的内存空间就跟大,程序正常使用需要申请使用的内存就会相应减少。

3. 内存抖动

  • 通常是指在短时间内发生了多次内存分配和释放,主要原因是短时间内频繁的创建对象,为了应对这种情况,虚拟机会频繁的出发 GC 操作,当 GC 进行时,其他线程会被挂起等待 GC 的完成,频繁 GC,也就导致比如 UI 在绘制时会超过 16 ms 一帧,导致画面卡顿等。


写在文末

纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游

好了, 关于 Java虚拟机垃圾回收机制内存泄漏/溢出/抖动 的 一点了解就介绍到这,各位看官食用愉快。


码字不易,如果本篇文章对您哪怕有一点点帮助,请不要吝啬您的点赞,我将持续带来更多优质文章。

发布了13 篇原创文章 · 获赞 14 · 访问量 8418

猜你喜欢

转载自blog.csdn.net/wild_onlyking/article/details/104723901