Java-JVM

2023/7/6

# Java程序是如何运行的?

# 1 JDK

  • HotSpot JVM:JDK的默认虚拟机(Oracle)
  • Java编译器(javac)
  • Java运行时环境jre(Java命令)

image

# 详细说明

  1. JDK中的Java编译器(javac)将Java源代码文件(.java文件)编译为字节码文件(.class文件)。编译器会检查源代码的语法和语义,然后将其转换为字节码形式。这些字节码文件包含了Java程序的中间表示,它们是平台无关的。
  2. JRE(Java Runtime Environment)包括Java虚拟机(JVM)和Java类库。JRE是Java程序的运行时环境,它负责加载和执行字节码文件。当你运行Java程序时,JVM会加载字节码文件,并使用解释器或即时编译器(JIT Compiler)将字节码转换为特定平台的机器码。JVM还负责管理内存、垃圾回收和线程等运行时任务。
  3. JVM将字节码转换为底层操作系统可执行的机器码。这个过程称为即时编译(Just-In-Time Compilation),其中JVM会根据代码的执行情况进行动态编译和优化。即时编译器将频繁执行的代码编译成本地机器码,以提高程序的执行性能。
  4. 生成的机器码是底层操作系统可执行的代码。操作系统负责加载和执行这些机器码,最终在计算机上运行Java程序。操作系统为Java程序提供了底层的硬件和系统资源访问能力。

综上所述,JDK中的Java编译器将Java源代码编译为字节码文件,JVM和JRE负责加载和执行字节码,并将其转换为底层操作系统可执行的机器码。操作系统最终负责执行这些机器码,并运行Java程序。这个过程保证了Java程序的跨平台性和可移植性。

# 2 垃圾回收机制 Garbage Collection

image

# 3 序列化、反序列化

  • 为了能在网络上传输、存储到文件中
  • 为了进程间的通信

序列化:

image

  1. 序列化是指将对象转换为可以在网络传输或存储中进行传递或持久化的格式
  2. 在序列化过程中,对象的状态(包括属性值和数据)被转换为字节流或其他可传输的格式
  3. 以便于跨系统、跨平台的数据交换和共享

反序列化:

image

  1. 序列化的逆过程,即将序列化后的数据重新转换为对象的过程

# java 中的Serializable

  • 实现 Serializable 接口是为了将对象进行序列化和反序列化
  • 该对象的类和其所有可访问的字段都需要是可序列化的。
  • 如果一个类中的某个字段不可序列化,可以使用 transient 关键字标记该字段,使其在序列化过程中被忽略。

# 1 如何判定哪些内存需要回收

在Java虚拟机的堆中会存放着很多的对象,那么,我们需要回收垃圾的时候,是通过什么算法来判断哪些垃圾的生命周期已到,需要回收呢?接下来的几种算法将帮助你解决这几个问题。

# 引用计数算法

先讲讲第一个算法:引用计数算法

其实,这个算法的思想非常的简单,一句话就是:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能再被使用的。

这些简单的算法现在是否还被大量的使用呢,其实,现在用的已经不多,没有被使用的最主要的原因是他有一个很大的缺点很难解决对象之间循环引用的问题

循环引用:当A有B的引用,B又有A的引用的时候,这个时候,即使A和B对象都为null,这个时候,引用计数算法也不会将他们进行垃圾回收。

 1/**
 2 * @ClassName Test_02
 3 * @Description
 4 * @Author lichengcan
 5 * @Date 2023/11/5 16:59
 6 * @Version 1.0
 7 **/
 8public class Test_02 {
 9
10    public static void main(String[] args) {
11        Instance instanceA = new Instance();
12        Instance instanceB = new Instance();
13
14        instanceA.instance = instanceB;
15        instanceB.instance = instanceA;
16
17        instanceA = null;
18        instanceB = null;
19
20        System.gc();
21
22        Scanner scanner = new Scanner(System.in);
23        scanner.next();
24    }
25}
26
27class Instance{
28    public Object instance = null;
29}

如果使用的是引用计数算法,这是不能被回收的,当然,现在的JVM是可以被回收的。

# 可达性分析算法

这个算法的思想也是很简单的,这里有一个概念叫做可达性分析,如果知道图的数据结构,这里可以把每一个对象当做图中的一个节点,我们把一个节点叫做GC Roots,如果一个节点到GC Roots没有任何的相连的路径,那么就说明这个节点不可达,也就是这个节点可以被回收。

上面图中,虽然obj7、8、9相互引用,但是到GC Roots不可达,所以,这种对象也是会被当做垃圾收集的。

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

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

# 2 什么时候回收

在可达性分析算法中不可达的对象,也不是一定会死亡的,它们暂时都处于“缓刑”阶段,要真正宣告一个对象“死亡”,至少要经历两次标记过程。

# step1:判断有没有必要执行finalize()方法

  • 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法

另外,有两种情况都视为“没有必要执行”:

  • 对象没有覆盖finaliza()方法。
  • finalize()方法已经被虚拟机调用过。

# step2:如何执行

如果这个对象被判定为有必要执行finalize()方法,那么此对象将会放置在一个叫做 F-Queue 的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。

# step3:执行死亡还是逃脱死亡

首先,我们需要知道,finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue 队列中的对象进行第二次小规模的标记。

  • 逃脱死亡:对象想在finalize()方法中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,例如把自己(this关键字)赋值给某个类变量或者对象的成员变量,这样在第二次标记时它将被移出“即将回收”的集合。
  • 执行死亡:对象没有执行逃脱死亡,那就是死亡了。

# 3 如何回收

如何回收其实就是利用哪些算法进行回收,垃圾收集算法这里讲几种大家平时也是看到的比较的算法,分别为:标记-清除算法复制算法标记-整理算法分代回收算法

这部分的内容其实在网上的文章比较多了,而且,基本上的差别不大,所以,从网上的文章选取下来,当做一个小的总结,大家可以参考这篇文章算是一个比较全的总结:GC算法与内存分配策略。

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

标记-清除(Mark-Sweep) 算法是最基础的垃圾收集算法,后续的收集算法都是基于它的思路并对其不足进行改进而得到的。顾名思义,算法分成“标记”、“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程在前一节讲述对象标记判定时已经讲过了。

标记-清除算法的不足主要有以下两点:

  • 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不触发另一次垃圾收集动作。
  • 效率问题,因为内存碎片的存在,操作会变得更加费时,因为查找下一个可用空闲块已不再是一个简单操作。

标记-清除算法的执行过程如下图所示:

# 复制(Copying)算法

为了解决标记-清除算法的效率问题,一种称为**“复制”(Copying)的收集算法出现了,思想为:它将可用内存按容量分成大小相等的两块**,每次只使用其中的一块。当这一块内存用完,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。

这样做使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,代价可能过高了。复制算法的执行过程如下图所示:

# 标记-整理(Mark-Compact)算法

复制算法在对象存活率较高时要进行较多的复制操作,效率将会变低。更关键的是:如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用复制算法

根据老年代的特点,标记-整理(Mark-Compact)算法被提出来,主要思想为:此算法的标记过程与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。 具体示意图如下所示:

# 分代收集(Generational Collection)算法

当前商业虚拟机的垃圾收集都采用分代收集(Generational Collection)算法,此算法相较于前几种没有什么新的特征,主要思想为:根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法:

  • 新生代 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
  • 老年代 在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清除标记-整理算法来进行回收。

# 二 linux安装jdk

# 2.1 yum安装

# 查询要安装jdk的版本

yum -y list java*

# 安装jdk1.8

yum install -y java-1.8.0-openjdk (opens new window).x86_64

java -version

# 2.2 只能Java不能javac

# 2.2.1 配置path路径

export PATH=$PATH:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.372.b07-1.el7_9.x86_64/jre/bin

# 2.2.2 安装devel

使用yum install java-1.8.0-openjdk命令安装jdk后,java命令和java -version的输出都是正确的,唯独javac没有作用。

解决:同时还要再安装java-1.8.0-openjdk-devel,执行yum install java-1.8.0-openjdk-devel安装完成后即可使用javac命令进行编译。

然后配置环境变量 vim /etc/profile

vim /etc/profile
#set java environment  
export JAVA_HOME=/usr/lib/jvm/java

export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar

export PATH=$PATH:$JAVA_HOME/bin