【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制

尽力做到十全十美~~

文章目录

  • 1. JVM内存区域划分
  • 2. 垃圾回收机制
    • 2.1 内存溢出与内存泄漏
    • 2.2 判断是否是垃圾
      • 2.2.1 引用计数
      • 2.2.2 可达性分析
    • 2.3 垃圾清理算法
      • 2.3.1 标记清除
      • 2.3.2 复制算法
      • 2.3.3 标记整理算法
      • 2.3.4 分代回收
  • 3. 触发GC的时机



1. JVM内存区域划分

JVM运行时数据区域,也叫内存布局。如下图,它由五大部分组成。
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第1张图片

  1. 本地方法栈,native指的是JVM内部的C++代码,这块空间是给调用native方法准备的栈空间,每个线程有一个自己的本地方法栈.
  2. 程序计数器,记录当前线程执行到了哪条指令.每个线程都有自己的一份程序计数器.
  3. 虚拟机栈,给Java代码使用的栈,每个线程都有一个自己的虚拟机栈.线程中的每个方法作为一个元素,称为一个栈桢.这个栈帧,包含方法的入口地址,返回地址,局部变量等.每调用一个方法,就会创建一个栈帧,方法执行结束,栈帧自动销毁.
  4. 堆,是整个JVM空间最大的区域,new出来的对象,类的成员变量,都在堆中.堆是每个进程有一个自己的堆
  5. 元数据区,之前也叫方法区,包含常量池,静态成员,一个进程有一块,进程中的多个线程公用这块空间.常量池中包含字面量及符号引用。字面量包括字符串,final常量及基本数据类型的值。符号引用包含在该类中,出现过的各类包,类,接口,字段,方法等元素的全限定名,符号引用,只是一个符号而已,只是告知jvm,此类需要哪些调用方法,引用或者继承哪些类等等信息.但要使用的时候,只知道名字是不行的,还需要知道符号的地址,直接引用就是这种指针,指向目标地址,类加载的解析阶段就是将符号引用转为直接引用。

2. 垃圾回收机制

2.1 内存溢出与内存泄漏

首先,我们来简单了解一下内存溢出与内存泄漏。

内存泄漏:我们在栈上占用的空间,方法结束,栈会自动销毁。但我们在堆上申请的空间(使用new,或者C++的malloc开辟的空间),如果没有手动释放,这块空间就会一直被占用,我们也用不了,相当于荒废了,这就可能导致一个严重的问题- - -内存泄漏。若程序24小时运转,这种内存泄漏越来越多,最终没有可用的空间,后果会非常严重。什么是垃圾呢,垃圾指的是不再使用的内容,垃圾回收就是不用的内容帮我们自动释放了。

内存溢出:程序在申请内存时,没有足够的内存空间供其使用,以下是有可能引起内存溢出的原因。

  1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
  2. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收,这是上面内存泄漏导致的问题。
  3. 代码中存在死循环或循环产生过多重复的对象实体;
  4. 使用的第三方软件中的BUG;
  5. 启动参数内存值设定的过小,如给一个Integer类型的数字存了long长度的值,存不下。

2.2 判断是否是垃圾

JVM虚拟机中虚拟机栈,程序计数器等是每个线程有一份,线程结束,内存空间会自动回收,这里的垃圾回收,我们主要针对堆上的内容。系统是怎么判断一个对象是否是垃圾的呢?当一块内存的内容我们不再使用,这个内存的内容就是垃圾了。GC回收以对象为单位。

那么GC是通过怎样的方法来判断一个对象是否是垃圾的呢?
关键看,这个对象有没有引用指向它,如果没有引用指向它,这个对象也找不到,没办法再使用了,就可以当作垃圾进行回收。

2.2.1 引用计数

这种办法是Python,PHP的做法,Java没有使用。
JVM给每一个对象分配了一个计数器,没有一个引用指向它,计数器加一。当计数器为0时,这个对象就可以被回收了。如下图所示.

Test t1 = new Test();//Test对象计数1
Test t2 = t1;//Test对象计数2
Test t3 = t2;//Test对象计数3

一旦对象超出作用域,失效了,计数就会减1.当计数器为0时,回收Test对象.
这种方法虽简单有效,但缺点也很明显,每一个对象都分配计数器,当对象很多的时候,就会占用大量空间.当同一个类里的两个对象互相引用,它们的计数器最少为1,无法被正确释放.

2.2.2 可达性分析

Java中的对象都是通过引用来访问的,经常一个引用,指向一个对象,这个对象的成员又指向别的对象.这种关系,JVM通过链式或者树将他们串起来.
每隔一段时间,从根节点遍历一次,没办法遍历到的,就作为垃圾处理.
在Java中,GCroot对象包含以下几种,虚拟机栈中的本地变量和参数、类静态属性引用的对象、常量池中的引用的对象、JNI 引用等.以上这些对象都是被认为是无需进行垃圾回收的,所以任何一个能够与GC Roots连接的对象都会被视为是存活对象,而不能够连接到GC Roots的对象则被认为是垃圾对象。一个代码中有很多这样的起点,把每个起点都往下遍历一遍,就完成了一次扫描.

2.3 垃圾清理算法

2.3.1 标记清除

标记所有不再使用的对象,标记完成后,统一回收.
如下图,回收之前
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第2张图片
回收之后,有很多无法使用的内存碎片.长时间后,会有大量的内存小碎片,如果申请大一点的内存空间,就会失败.
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第3张图片

2.3.2 复制算法

这种算法是把内存分成大小相等的两部分,只用某一半,当内存需要垃圾回收时,把还存活的对象复制到另一半,之后,把这块空间全部释放.
如下图,这是释放之前的
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第4张图片
将存货对象复制到右半块,然后,将这左半块空间整个释放掉.
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第5张图片
优点是防止了内存碎片,缺点是,每一次只能使用一半的内存空间,空间利用率低,并且,在垃圾少,存活对象多时,需要搬运大量的存活对象,效率低.

2.3.3 标记整理算法

为了优化复制算法,标记整理算法的做法是,先标记需要回收的对象,之后,再把存活的对象都移向一端,之后,清理边界之外的内存,如下图所示.
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第6张图片
优点是避免内存碎片,但效率依旧不是很高

2.3.4 分代回收

分代回收,是基于不同类型的对象,来进行不同方法的回收.JVM有一个规律,一个对象,它的生命周期,要么特别短,要么特别长.如果,一个对象它存在的时间很长,那么,他大概率会继续长时间存在下去.
依此,我们给对象引入一个概念- - -年龄,以熬过GC的轮次为单位,经过一轮可达性的遍历,这个对象仍然存在,它的年龄就加1.
如下图,是堆的区域划分.
【JavaEE 初阶】 JVM内存区域划分与GC垃圾回收机制_第7张图片

  1. 刚new出来的对象,是年龄为0的对象,放到伊甸区(是西方神话中造小人的地方)
  2. 若熬过一轮GC,对象经复制算法放到幸存区.
    在幸存区,要接受两个幸存区周期性的GC检验,每一轮GC,若是垃圾,就释放掉,不是垃圾,就经复制算法,拷贝到另一个幸存区.同一时间,只能使用一个幸存区.
    若一个对象,在幸存区被拷贝多次,就可以经复制算法进入老年区了
  3. 在老年区的对象,生命周期普遍更长,针对老年区的对象,也要进行GC可达性分析,但频率可以降低.若是垃圾,可通过标记清理的方式进行清除.

3. 触发GC的时机

  1. 当内存中没有更多的空间分配给新的对象时。
  2. 当程序调用System.gc()方法时,建议而非强制进行垃圾回收。
  3. 当使用了大量的临时对象,这些对象可能已经成为垃圾。

你可能感兴趣的:(Javaee初阶,jvm,java,java-ee)