【Java相关】GC垃圾回收和内存分区


先说一下,HotSpot是目前应用最广泛的一种Java虚拟机。说起垃圾回收,首先我们需要复习一下JVM的内存分配。

一、内存分区

Java虚拟机把内存划分为若干个不同的数据区,如下图所示:

在这里插入图片描述

接下来将会详细介绍这些分区。

1.1 程序计数器( Program Counter Register)

  • 线程私有

程序计数器 是一块较小的内存空间, 它可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令, 分支、 循环、 跳转、 异常处理、 线程恢复等基础功能都需要依赖这个计数器来完成。

为了线程切换后能恢复到正确的执行位置,保证上下文切换的正确性, 每条线程都需要有一个独立的程序计数器, 各条线程之间计数器互不影响, 独立存储, 我们称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地址; 如果正在执行的是Native方法, 这个计数器值则为空( Undefined) 。 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

1.2 Java虚拟机栈( Java Virtual Machine Stacks)

  • 线程私有
  • 数据结构为栈帧

它的生命周期与线程相同。

每个Java方法在执行的同时都会创建一个栈帧用于存局部变量表、 操作数栈、 动态链接、 方法出口等信息。 每一个方法从调用到执行完成的过程, 就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表存放了

  • 编译期可知的各种基本数据类型( boolean、 byte、 char、 short、 int、float、 long、 double)
  • 对象引用( reference类型, 它不等同于对象本身, 可能是一个指向对象起始地址的引用指针, 也可能是指向一个代表对象的句柄或其他与此对象相关的位置) 和returnAddress类型( 指向了一条字节码指令的地址) 。
  • 在方法运行期间不会改变局部变量表的大小。

1.3 本地方法栈( Native Method Stack)

它与虚拟机栈非常相似的, 区别:

  • 虚拟机栈为虚拟机执行Java方法( 也就是字节码) 服务
  • 而本地方法栈则为虚拟机使用到的Native方法服务。

1.4 Java堆( Java Heap)

  • 线程共享

此内存区域的唯一目的就是存放对象实例

Java堆是垃圾收集器管理的主要区域,Java堆中还可以细分为: 新生代和老年代; 再细致一点的有Eden空间、 From Survivor空间、 To Survivor空间等。

补充——栈内存:

Java Stack内存用于执行线程。

每当调用方法时,都会在磁盘存储中创建一个新块,以容纳该方法的本地原始值并引用该方法中的其他对象。

存放基本类型的变量对象的引用方法调用,遵循先入后出的原则。

  • Java中的代码是在函数体中执行的,每个函数主体都会被放在栈内存中,比如main函数。假如main函数里调用了其他的函数,比如add(),那么在栈里面的的存储就是最底层是main,mian上面是add。栈的运行时后入先出的,所以会执行时会先销毁add,再销毁main。

栈的优势是,栈内存与堆内存相比是非常小的,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。栈有一个很重要的特殊性,就是存在栈中的数据可以共享。

1.5 方法区( Method Area)

  • 线程共享

方法区存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。永久代就存放在方法区。

垃圾收集行为在这个区域是比较少出现的, 但并非数据进入了方法区就如永久代的名字一样“永久”存在了。 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

1.6 运行时常量池

运行时常量池( Runtime Constant Pool) 是方法区的一部分。 Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外, 还有一项信息是常量池( Constant Pool Table) , 用于存放编译期生成的各种字面量和符号引用, 这部分内容将在类加载后进入方法区的运行时常量池中存放。

1.7 总结

在这里插入图片描述

主要分两大块,一块是被线程所共享的,另外一块是线程所独享的区域

  • 被线程所共享的区域,主要有两大块,一块是堆内存,另一块是方法区
  • 被线程所独享的区域主要有三大块,一块是栈内存,第二块叫本地方法栈,第三块叫程序计数器。

其中,在堆内存内部,又会分为两块区域,也就是我们经常听到的,叫做新生代和老年代,新生代又会被进行再划分,划分成一般是三个或者四个区域:

  1. 第一个是Eden,英文翻译过来是伊甸园,它是干嘛的呢?我们只要是在创建对象的过程中,创建一个,那个这个对象就会扔到Eden这块区域中,只要是新创建的,那么就会扔到Eden中,让他们在那里过无忧无虑的生活,当然了,垃圾回收器也是最喜欢光顾Eden的,那么,一旦被垃圾回收所定位了,那么它就不再在Eden中了,那么,可能就被杀掉了,如果没被杀掉,就被放到Survivor即存活区中。
  2. Survivor即存活区中那么就相当于,我有诺亚方舟的船票,那么我就进到了存活区里面,如果没有,就被杀掉了,还有一种就是,我多次在存活区里面,那么可能,我一直可能会被,地位比较高,在这里面上升了,升到哪里去了呢?老了,最后也没退休,就进入到Tenured Gen这一块区域中。
  3. Tenured Gen这一块区域是去养老去了,基本上,我们的垃圾回收比较少关注Tenured Gen这一块区域,这是新生代三块内存区域的划分。

在后续的垃圾收集算法我会继续提到上述概念。

二、什么时候GC?

垃圾收集GC需要完成的三件事情:

  1. 什么时候GC?
  2. 谁是垃圾?
  3. 如何回收?

在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

大多数情况下, 对象在新生代Eden区中分配。 当Eden区没有足够空间进行分配时, 虚拟机将发起一次 GC。

三、谁是垃圾?垃圾判断算法

GC 主要处理的是对象的回收,什么时候会触发一个对象的回收的呢?

  1. 对象没有引用
  2. 作用域发生未捕获异常
  3. 程序在作用域正常执行完毕
  4. 程序执行了System.exit()
  5. 程序发生意外终止(被杀进程等)

如何决定哪些对象是垃圾?涉及到以下算法:

3.1 引用计数法

在JDK1.2之前,使用的是引用计数器算法。通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被用到,那么这个对象就可被回收了。

这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,如下面所示:

public class Main {
   
    public static void main(String[] args) {
   
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
         
        object1.object = object2;
        object2.object = object1;
         
        object1 = null;
        object2 = null;
    }
}
 
class MyObject{
   
    public Object object = null;
}

object1 和 object2 赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。所以主流的JVM没有使用这种算法。

为了解决这个问题,在Java中采取了 可达性分析法。

3.2 可达性分析法

基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点,根据引用关系向下搜索, 搜索过程所走过的路径称为“引用链”(Reference Chain) , 如果没有任何引用链相连,则证明此对象是不可能再被使用的。

如图所示, 对象object 5、 object 6、 object 7虽然互有关联, 但是它们到GC Roots是不可达的,因此它们将会被判定为可回收的对象。

在这里插入图片描述

可作为GC Roots的对象包括下面几种:

  1. 虚拟机栈( 栈帧中的本地变量表) 中引用的对象。他们引用到的对象也就不会被回收。
public class 
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值