Java学习笔记之JVM篇

Java学习笔记之JVM篇

    • 前言
    • 问:什么是JVM?
    • 问:JRE/JDK/JVM是什么关系?
    • 判断对象是否存活的算法
    • 问:如何回收对象
    • 垃圾收集算法(4种)
      • 标记-清除(Mark-Sweep)算法
      • 复制算法(为解决标记-清除算法的效率问题)
      • 标记-整理算法(解决复制算法不可用于老年代问题)
      • 分代收集算法(主流算法)

前言

这是JVM学习笔记,内容主要来自 周志明编写的《深入理解Java虚拟机 JVM高级特性与最佳实践》(第2版)一书,相当于是此书的读书笔记,还有一些内容引用自公众号内容

问:什么是JVM?

答:JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。

问:JRE/JDK/JVM是什么关系?

答:JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
**JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。**它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
**JDK(Java Development Kit,Java开发工具)**是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
Java学习笔记之JVM篇_第1张图片
延伸:J2EE、J2SE、J2ME

  • J2EE:Java 2 Platform Enterprise Edition 企业版,用于企业应用,支持分布式部署。

  • J2SE:Java 2 Platform Standard Edition 标准版,用于桌面应用,,也是J2EE的基础。

  • J2ME:Java 2 Platform Micro Edition 移动版用于小型设备(移动端开发、机顶盒),是J2SE的一个子集。

说明:
J2SE包含于J2EE中,J2ME包含了J2SE的核心类,但新添加了一些专有类 。
J2SE包含那些构成Java语言核心的类,比如数据库连接、接口定义、输入/输出、网络编程。笼统的讲扩充它,再加些EJB等企业应用方面就是J2EE,压缩它再加些CLDC等方面特性就是J2ME

判断对象是否存活的算法

  1. 引用计数算法(没有选用)
    基本思想:给对象添加一个应用计数器,每当有个地方引用它时,计数器值就加1;当引用失效时计数器值就减1;任何时候计数器为0的对象就是不可能再被引用的。
    缺陷:Java虚拟机中没有选用引用计数器算法来管理内存,原因是该算法难以解决对象之间相互循环引用问题(这个问题会导致对象的引用计数器都不为0,无法通知GC回收它们),并且引用计数器增加了程序的开销
  2. 可达性分析算法(使用)
    基本思想:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,每当一个对象到GC Roots没有任何引用链相连时(用图论的话来说就是从GC Roots不可到达这个对象),则证明此对象是不可用的(注意的是此时只是判定为可回收对象,真正回收至少需要两次标记)

在Java语言中,可作为GC Roots的对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

延伸:引用种类(4种)
JDK1.2之后Java对引用进行扩充:强引用(Strong Reference)> 软引用(Soft Reference) > 弱引用(Weak Reference) > 虚引用(Phantom Reference)

强引用:只要强引用(类似Object object = new Object()这类的)还存在,垃圾回收器就永远不会回收

软引用:描述一些有用但是非必需的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

弱引用:描述非必需对象,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用:最弱放入一种引用关系,一个对象是否有徐引用的存在完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的是能在这个对象被收集器回收时收到一个系统通知。

问:如何回收对象

即使在可达性分析算法中不可达对象,也并非是“非死不可”的,这时候他们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经过两次标记过程
具体过程:如果对象在进行可达性分析后发现没有与GC Roots相连的引用链,那么它会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法(因为如果覆盖了finalize()方法那么该方法要等被调用,没有覆盖说明已经被自动调用过了)或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都是为“没有必要执行”。如果整个对象被判定为有必要执行finalize()方法,那么这个对象将会放到一个叫F-Queue队列中。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()方法中成功拯救自己,即只要重新与引用链上的任何一个对象建立关联即可,比如把自己,也就是this关键字赋值给某一个类变量或者对象的成员变量那么在第二次标记时他会被清除出“即将回收”的集合;如果对象整个时候还没有逃脱那么基本上它就真的被回收了。

注意:对象自救的机会只有一次,因为任何一个对象的finalize()方法最多只会被系统自动调用一次。一旦对象变为finalized就再怎么也不会回到F-Queue中去了,当然也就没有机会去执行finalize()方法(这时候就会被判定为没有必要执行,就要面临下一次回收)

垃圾收集算法(4种)

Java学习笔记之JVM篇_第2张图片

标记-清除(Mark-Sweep)算法

Java学习笔记之JVM篇_第3张图片
算法思想:分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象(详见如何回收对象)
不足

  • 效率问题:“标记”和“清除”两个过程的效率都不高
  • 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。

应用场景: 针对老年代的CMS收集器;

复制算法(为解决标记-清除算法的效率问题)

算法思想: 将可用内存按容量大小分为相等的两块,每次只是用其中一块。当这块内存用完了就将还存活的对象复制到另一块的上面然后再把已使用的内存空间一次性清理掉
不足:

  • 可用内存缩小为原来一半
  • 在对象存活较高时就要进行较多的复制操作,效率将会变低

说明:如果不想浪费50%的空间就需要额外的空间进行分配担保,以应对被使用内存中所有对象都100%存活的极端情况,所以该算法可用于回收新生代(因为新生代每次收集时都有大批对象死去,只有少量存活,复制效率当然大于对象存活率高的老年代),不可用于回收老年代

标记-整理算法(解决复制算法不可用于老年代问题)

Java学习笔记之JVM篇_第4张图片
过程:标记过程同上,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法(主流算法)

Java中对一般分为新生代和老年代,分代收集算法根据存活周期的不同将内存分为几块,根据各个年代的特点采用适合的收集算法。
新生代:复制算法
老年代:标记-清除算法或标记-整理算法

未完待续…

你可能感兴趣的:(Java基础)