jvm---执行引擎、垃圾回收

目录

    • 一、 执行引擎
      • 1、为什么JVM执行引擎设计为半解释型,半编译型?
    • 二、垃圾回收
      • 1、概述
      • 2、垃圾回收:
      • 3、什么是垃圾?
      • 4、为什么需要GC?
      • 5、早期垃圾回收
      • 6、垃圾回收机制
      • 7、 Java 堆是垃圾收集器的工作重点
    • 三、内存溢出与内存泄漏
    • 四、垃圾收集算法分为两大类
      • 1、垃圾标记阶段算法
        • 1、引用计数算法(在jvm中不被使用)
        • 2.可达性分析算法 / 根搜索算法(这是java目前所使用的垃圾标记算法)
        • (1) 哪些引用被用来当做根;
        • (2)finalize() 方法机制
      • 2、垃圾回收阶段算法
        • 1.标记-清除算法
        • 2. 复制算法
        • 3.标记压缩算法
      • 3、标记-清除 和 标记-压缩对比
      • 4、分代/分区收集
    • 五、垃圾回收中的相关概念
      • 1、System.gc()的理解
      • 2、STOP THE WORLD
      • 3、对象引用
      • 4、垃圾回收器
      • (1)垃圾回收器分类
      • (2)垃圾回收器的性能指标
      • (3) CMS 回收器(并发收集、低延迟)

一、 执行引擎

前端编译(.java --> .class)字节码 不等于 机器码,需要jvm将字节码加载到内存中,需要通过执行引擎将字节码 解释/编译成机器码 后端编译(.class --> 机器码)。

执行引擎机制:

  • 解释器 :将字节码逐行解释执行
  • JIT编译器(即时编译器):将字节码整体编译为机器码执行

1、为什么JVM执行引擎设计为半解释型,半编译型?

逐行解释执行效率低
JVM会针对使用效率较高的热点代码进行编译,并缓存起来,执行效率提高
但是编译是需要消耗时间的。
所以jvm刚刚启动后,可以先通过解释器,解释执行代码
之后在使用编译器编译执行,两种结合在一起

二、垃圾回收

jvm---执行引擎、垃圾回收_第1张图片

1、概述

垃圾收集机制并不是java语言创的,但是又是java的招牌,java可以自动垃圾回收

2、垃圾回收:

回收哪些区域:频繁回收内存,较少回收方法区,栈(溢出),本地方法栈(溢出),程序计数器没有垃圾回收。

3、什么是垃圾?

    while (true){
            new Random().nextInt();
        }

垃圾是指在运行程序中没有任何引用指向的对象,这个对象就是需要被回收的垃圾。

4、为什么需要GC?

(1)垃圾如果不及时清理,越积越多,可能会导致内存溢出。
(2)垃圾多了,内存碎片较多,例如数组,需要连续空间
(3)随着应用程序应付的业务越来越大,没有GC就不能保证应用程序的正常进行。

5、早期垃圾回收

早期是手动回收不被使用的对象,例如C++,java语言是自动垃圾收集的。

6、垃圾回收机制

自动内存管理:无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险。以更专心地专注于业务开发
自动收集的担忧:自动回收方便了程序员的开发,但是降低处理内存问题的能力。
自动虽好,但是还是应该了解并掌握一些相关内存管理知识。

7、 Java 堆是垃圾收集器的工作重点

从次数上讲:
频繁收集 Young 区
较少收集 Old 区
基本不收集元空间(方法区

三、内存溢出与内存泄漏

溢出:内存不够用了
泄漏:有些对象已经在程序不被使用了,但是垃圾回收机制并不能判定其为垃圾对象,不能将其回收掉,这样的对象越积越多,长久也会导致内存不够用。

eg:
与数据库连接完之后,需要关闭连接通道,但是没有关闭。
io读写完成后没有关闭

四、垃圾收集算法分为两大类

1、垃圾标记阶段算法

主要是来判定哪些对象已经不再被使用,标记为垃圾对象,
判定对象为垃圾的标准:不被任何引用所指向的对象。Object obj =new Object();

1、引用计数算法(在jvm中不被使用)

如果有一个引用指向此对象,那么计数器加1,如果没有引用指向,计数器为0,此时就判定位垃圾。

优点:方便使用,设计简洁
缺点:增加了计数器的存储空间,计数需要消耗时间。

  • 导致了循环引用问题,好几个对象之间相互引用,但是没有其他引用指向他们,此时垃圾回收不能回收他们,但是也没有引用指向,这就造成了内存泄漏。
Object obj = new Object();
		obj =null;

jvm---执行引擎、垃圾回收_第2张图片

2.可达性分析算法 / 根搜索算法(这是java目前所使用的垃圾标记算法)

解决:循环引用问题 ,设计简单,运行高效,防止内存泄漏
思路:
从一些活跃引用(GCRoots根)开始,如果对象被根直接或间接引用,那么此对象不是垃圾,否则被标记为垃圾对象。
jvm---执行引擎、垃圾回收_第3张图片

(1) 哪些引用被用来当做根;

虚拟机栈中引用的对象(方法中引用的对象)
本地方法栈中引用的对象
静态变量所引用的对象
常量引用指向的对象
被synchronized当做锁的对象
java虚拟机内部的引用

  • 总结:栈中引用的(正在使用的)方法区,常量池中(生命周期较长的),被synchronized当做锁的对象。
(2)finalize() 方法机制
  • final(关键字) finally(代码块) finalize(方法)是object类中的一个方法,在对象被最终回收之前调用,只调用一次

java运行对象在销毁前去调用finalize(),去处理一些逻辑,一般不用(不建议用).不要显示的去调用finalize()方法,在里面写代码一定要慎重!!!

  • 在finalize()时,可能会导致对象复活。
  • finalize()由垃圾回收器调用,没有固定的时间。
  • 一个糟糕的finalize() 会严重影响GC性能。比如finalize 是个死循环。

垃圾:不是立刻被回收,只是被标记,有了finalize()方法/机制的存在,我们的对象有可能起死回生。

对象状态:
可触及的:从根节点开始,可以到达这个对象。(没有被标记为垃圾)
可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活。确定为垃圾了,但没有调用finalize()方法。
不可触及的:对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会内调用一次

package com.ffyc.database.jvm.gc;

public class CanReliveObj {

	public static CanReliveObj obj;//类变量,属于 GC Root
	//此方法只能被调用一次
	 @Override
	protected void finalize() throws Throwable {
		//super.finalize();
		System.out.println("调用当前类重写的finalize()方法");
		obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
	}
	public static void main(String[] args) {
		try {
			obj = new CanReliveObj();
			// 对象第一次成功拯救自己
			obj = null;
			System.gc();//调用垃圾回收器,触发FULL GC  也不是调用后立刻就回收的,因为线程的执行权在操作系统
			System.out.println("第1次 gc");
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}

			System.out.println("第2次 gc");
			// 下面这段代码与上面的完全相同,但是这次自救却失败了
			obj = null;
			System.gc();
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

2、垃圾回收阶段算法

1.标记-清除算法
  • 分为两个阶段:
  1. 标记阶段:标记出从根可达的对象,标记的是被引用的对象。
  2. 清除阶段:此清除并非直接将垃圾对象清除掉,而是将垃圾对象的地址维护到一个空闲列表中。之后如果有新的对象产生,判断空闲列表中的对象空间能否存放的下新的对象,如果能放的下,那么就覆盖垃圾对象。

优点:简单、容易理解
缺点:效率低,会产生STW(在回收时,停止整个应用程序),会产生内存碎片。

2. 复制算法

将内存分为大小相等的两块,每次只使用其中的一块儿区域即可。当回收时,将不是垃圾的对象,复制到另一块内存中,排放整齐。然后将原来的内存块清空。减少内存碎片。

  • 优点:运行高效,减少内存碎片
  • 缺点:用到两倍的内存空间,对于G1垃圾回收器,将每个区域又可以拆分成更多的小区域。需要维护各区之间的关系。

一般在新生代中的幸存者0和幸存者1这两个区域使用复制算法。

3.标记压缩算法
  • 背景:复制算法需要移动对象位置,移动的数量如果多的情况下,效率低。对于年轻代来讲还是不错的。对于老年代大量的对象是存活的,如果需要移动就比较麻烦效率低。
  • 实现: 将存活对象标记处来,重新在本内存空间中排放位置。清除其他空间的垃圾对象。

3、标记-清除 和 标记-压缩对比

标记-清除是不移动对象,不会把垃圾对象清除掉(维护在一个空闲列表中)
标记-压缩是要移动对象的,要清除掉垃圾对象。

  • 优点:
    不会像标记-清除算法那样会产生内存碎片;
    不会像复制算法那样需要两块内存空间;
  • 缺点:
    效率相对低,对象位置移动后需要重新设置对象地址,也会有STW

4、分代/分区收集

由于对象的生命周期长短不同,将不同的对象存储在不同的区域;针对不同的区域进行分区收集,提高收集效率。
jvm---执行引擎、垃圾回收_第4张图片

五、垃圾回收中的相关概念

1、System.gc()的理解

调用System.gc()方法,会触发Full GC(整堆收集),但是不一定调用后会立刻生效,因为垃圾回收是自动的,一般情况下,不要在项目中显示的去调用。

2、STOP THE WORLD

stop the world —> STW 在垃圾回收时,会导致整个应用程序停止。
在标记垃圾对象时,需要以某个时间节点上内存中的情况进行分析(拍照 快照),因为不进行停顿的话,内存中的对象不停的变化,导致分析结果不准确。停顿是不可避免的,优秀的垃圾回收器尽可能减少停顿的时间。

3、对象引用

Object obj = new Object();
就是将对象分等级:强引用(有引用指向的对象)、软引用 、 弱引用 、虚引用(这三个都是垃圾了) 【在 java.lang.ref包中】

  • 强引用:obj引用创建的对象,那么此对象就是被强引用的,这种情况下,即使内存不够用了,报内存溢出OOM,也不会回收
  • 软引用:当内存足够使用时,先不回收这类对象,当虚拟机内存不够用时,要回收此类对象。
  • 弱引用:此类对象只能生存到下次垃圾回收时,只要发生垃圾回收,回收此类对象。
  • 虚引用:发现即回收。

4、垃圾回收器

比较底层,了解垃圾回收器的一些种类及实现。
垃圾回收器(具体实现垃圾回收的收集器名称)jvm---执行引擎、垃圾回收_第5张图片

(1)垃圾回收器分类

按线程数分,可以分为串行垃圾回收器和并行垃圾回收器。
按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。
按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器。

(2)垃圾回收器的性能指标

吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)
垃圾收集开销:垃圾收集所有时间与总运行时间的比例。
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
收集频率:相对于应用程序的执行,收集操作发生的频率。
内存占用:java堆区所占的内存大小。
快速:一个对象从诞生到被回收所经历的时间。

(3) CMS 回收器(并发收集、低延迟)

jvm---执行引擎、垃圾回收_第6张图片

你可能感兴趣的:(jvm,java)