13.GC垃圾回收
13.1.JVM内存
- Java虚拟机在Java程序运行过程中会把它管理的内存,划分为若干个不同的数据区域。这些区域各自的用途,以及创建和销毁的时间,有的区域随虚拟机的启动而存在,有些区域依赖用户线程的启动和结束而建立和销毁。(比如说栈),根据《Java虚拟机规范》内存Java虚拟机所管理的内存将会包括如上图的几个运行时数据区。
- 程序计数器:程序计数器(Program Counter Register)是一块较小的内 存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。(每个线程都有自己的程序计数器,线程隔离)
- Java虚拟机栈:它描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame )用于存储局部变量表、操作栈、动态链接、方法出口等信息。线程私有(线程隔离)
- 每个线程包含一个栈区,栈中只保存方法中(不包括对象的成员变量)的基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中
- 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
- 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
- 本地方法栈(线程私有):本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
- Java堆:
- 此内存区域的唯一目的就是存放对象,一个JVM实例只存在一个堆,堆内存的大小是可以调节的。堆内存是线程共享的。
- 存储的全部是对象实例,每个对象都包含一个与之对应的class的信息(class信息存放在方法区)。
- jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身,几乎所有的对象实例和数组都在堆中分配。
- 方法区(线程共享):方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量 、静态变量等数据,即时编译器编译后的代码等数据。又叫静态区,跟堆一样,被所有的线程共享。
13.2.内存管理的方式
- 显式的内存管理(C/C++)
- 内存管理(内存的申请和释放)是程序开发者的职责
- 常见问题:
- 内存泄漏:内存空间已经申请,使用完毕后未主动释放
- 野指针:使用了一个指针,但是该指针指向的内存空间,已经被free
- 隐式的内存管理(Java/C#)
- 内存的管理是由垃圾回收器自动管理的
- 优点:增加了程序的可靠性,减小了memory leak
- 缺点:无法控制GC的时间,耗费系统性能
- 内存的管理是由垃圾回收器自动管理的
13.3.GC
- GC——Garbage Collection,在JVM中,GC的功能是由垃圾回收器来完成。
- 一个对象在没有任何引用变量指向他,也无法访问到的时候,他就变成了垃圾。
13.3.1.垃圾判断算法
- 计数法:
- 给对象添加一个引用计数器
- 每当一个地方引用它时,计数器加1
- 每当引用失效时,计数器减少1
- 当计数器的数值为0时,也就是对象无法被引用时,表明对象不可在使用
无法解决循环引用的问题:如,A指向B,且B指向A,二者没有任何其他引用变量指向这两个对象,虽然计数器不为0,但是仍然是垃圾。
- 根搜索算法:
- 这个算法的基本思想是将一系列称为“GC Roots”的对象作为起始点
- 从这些节点开始向下搜索
- 搜索所走的路径称为引用链
- 当一个对象到所有的GC root之间没有任何引用链相连时,就认为该对象变成了垃圾
GC Roots包含对象:
- 虚拟机栈中引用的对象
- 栈中的变量是局部变量,直接访问到的对象不是垃圾
- 即:如果一个引用变量同时也是局部变量的话,该对象是一定可以访问到的对象。
- 方法区中的静态属性引用的对象
- 直接可以采取类名.的方法访问到,访问引用类型的静态变量
13.3.2.垃圾回收算法
- 标记清除算法(Mark Sweep)
- 简单的理解为,被判断为垃圾的对象会被标记,直接清除带有标记的即可,
- 优点:实现简单,回收效率高
- 缺点:造成内存的碎片化,无法满足程序中大块内存的需求
- 简单的理解为,被判断为垃圾的对象会被标记,直接清除带有标记的即可,
- 标记复制算法(Copy)
- 保留一部分作为保留区,将另一部分的内存采取标记清除算法,然后将清除完毕的区域再次作为保留区,并将存活对象复制按序排列在原保留区
- 优点:不会产生内存碎片,如果在内存回收的时候,只有少量存活对象,那么回收效率是很高的
- 缺点:内存利用率低,如果在内存回收的时候,有大量的存活对象,那么此时就要复制大量存活对象,回收效率低
- 保留一部分作为保留区,将另一部分的内存采取标记清除算法,然后将清除完毕的区域再次作为保留区,并将存活对象复制按序排列在原保留区
- 标记整理算法(Mark Compact)
- 清除带有标记的垃圾后,整理存活对象,按序存放存活对象到一端
- 优点:不会产生内存碎片,内存利用率高
- 缺点:效率低,用时间换空间
- 清除带有标记的垃圾后,整理存活对象,按序存放存活对象到一端
- 分代收集算法(Generational Collection 商用)
- IBM统计结论,绝大部分(98%)对象都是朝生夕死。
- 分代收集算法,把堆内存分为新生代和老年代,再把新生代分为两个站10%的survivor区域和一个80%的Eden区域,新生代的特征是非常容易死亡,因此非常适合使用标记复制算法,保留区域只使用两个survivor区域,每次回收只使用一个survivor区域。
- 如果一个新生代对象经过了连续15此的垃圾回收后,仍然存活,那么这种对象就具有了老而不死的特征,这种对象就会被移动到老年代中来存储,老年代使用标记整理算法。
13.3.3.GC相关概念
- Shallow size
- 就是对象本身占用的内存大小,也就是对象头加成员变量占用内存大小的总和
- Retained size
- 是该对象自己的shallow size 加上仅可以从该对象访问(直接或者间接访问)的对象的shallow size之和。
- 是该对象被GC之后所能回收的内存的总和。
13.3.4.GC触发的时机
- 申请heap space(堆内存)失败后会触发GC回收
- 系统进入idle(空闲)后一段时间会进行回收
- 主动调用System.GC进行回收
两种内存溢出错误Out of Memory case:
- Heap OOM 堆溢出
- Stack Overflow 栈溢出
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181068.html