JVM

41 阅读6分钟

概念

JVM是一个软件模拟的机器(处理器、堆、栈、指令系统都有),因为是软件模拟,因此在任何一个实现Java虚拟机的机器上,都可以运行Java程序,Java从此可以跨平台。

JVM随Java程序启动而启动,随Java程序结束而结束,每个Java程序都会运行一个单独的JVM。exit 指令可以结束Java程序,也可以结束JVM。

Java虚拟机定义了内部结构,也严格定义了外部行为。

每一个Java虚拟机都有一个类加载子系统,负责加载相关的类型(类和接口)并赋唯一的名称,每一个Java虚拟机还有一个代码执行引擎,负责执行被加载类型中包含的指令。

Java虚拟机中使用的数据类型都严格定义,分为原始数据类型和引用数据类型。Java虚拟机中还有一个引用类型,不在Java语言中被使用,仅在Java虚拟机中被使用,用来实现 finally classes。、

JVM 内存划分

程序运行时数据区:

线程共享:方法区 堆

线程私有:虚拟机栈 本地方法栈 程序计数器

程序计数器:计算当前程序执行到Java字节码的哪一行了。

虚拟机栈:函数调用时需要使用栈来进行入栈标记,虚拟机栈用来执行非本地方法的方法

本地方法栈:用来执行本地方法的栈

堆:用来存储对象实例,垃圾收集器的主要收集区域

方法区:用来存储类信息,静态变量,常量,class文件。

虚拟机类加载机制

Java中类的连接和加载是在运行过程中完成的。

1、加载

  1. 通过一个类的全限定名来加载类的二进制字节码
  2. 将二进制字节码所代表的静态存储信息转化为方法区的运行时数据结构
  3. 在堆上创建一个实体类作为方法区数据的访问入口

2、验证

  1. 虚拟机规范:class文件的存储格式正确
  2. 文件格式验证:class文件的文件格式正确,可以被当前版本的虚拟机运行
  3. 元数据验证:Java代码符合Java规范
  4. 字节码验证:数据流和控制流不会危害到Java虚拟机
  5. 符号引用验证:在符号引用转化为直接引用时,确保常量池中存在与之匹配的信息

3、准备

为类变量分配内存,并设置初始值,这部分内存分配在方法区。

例如常量就会在这个阶段被分配内存并设置初始值。

4、解析

将符号引用转化为直接引用

符号引用:一个字符串,用来描述被引用的对象,可能没有被加载到内存中。

直接引用:一个句柄、指针或者偏移量,指向被引用的对象且此对象一定被加载到内存中。

5、初始化

调用 <cinit> 初始化,<cinit><init> 不同,init 是实例构造器,需要调用父接口的init,而cinit不需要调用父接口的cinit

cinit 本质上由类中的赋值语句和静态语句块组成

JVM会保证在多线程环境中cinit的正确性

6、使用

7、卸载

类的主动引用

  1. 使用字节码指令new创建一个实例时
  2. 使用字节码指令getstaticsetstatic获得或设置一个类的静态字段时
  3. 使用字节码指令invokestatic执行一个类的静态方法时
  4. 使用Java反射包的方法对类进行反射调用时
  5. 虚拟机启动时,初始化主类

类的被动引用

使用子类来调用父类的静态方法不会导致子类被初始化。(对于静态字段,只有直接定义这个字段的类才会被初始化)

通过数组定义类、应用类,会触发一个Lclass类的初始化,此类直接继承object,创建动作的字节码:newarray

常量会在编译阶段存储至调用类的常量池。

判断对象是否存活

可达性算法(根搜索算法)

可以作为根的有:

  1. 方法区中的常量引用的对象
  2. 方法区中静态属性引用的对象
  3. 虚拟机栈中引用的对象
  4. 本地方法栈中引用的对象

对象的引用

强引用:只要引用存在,则对象永远都不会被垃圾回收器回收

例如:object obj = new object() 只要obj存在,则对象不会被回收

软引用: 内存不够时会被垃圾回收器回收, 软引用可以提升访问速度,因为被创建在内存中,而不需要去类的真实来源去查询类信息。

object obj = new object();
SoftReference sf = new SoftReference(obj);
obj = null; // 去掉强引用
sf.get(); // 获取对象

弱引用:用来判断对象是否被垃圾回收器标记 WeakReference

虚引用:用来判断对象是否已经从内存中删除 PhantomReference<object>

分代垃圾回收

Java 语言中没有提供显示分配和释放内存的操作,程序员一般将引用设置为null让垃圾回收机制释放内存或者使用System.gc释放内存,不过 System.gc 比较损耗性能

分代垃圾回收基于的两个假设:

  1. 大部分对象在短时间内不可达(朝生夕灭)
  2. 只有少部分年老对象会引用年轻对象

Java将内存区分为年轻代、年老代和永久代。

Minor GC : 一个对象从年轻代消失,被称为发生了一次 Minor GC

Major GC : 一个对象从年老代消失,被称为发生了一次 Major GC

年轻代的组成部分:一个Eden区,两个Survivor区

新创建的对象会在Eden区,一次GC后,存活的对象转移到其中一个Survivor区,当此Survivor区的满了之后,存活的对象转移到另一个Survivor区,并将之前满了的Survivor区清空。如此多次之后仍然存活的对象转移到年老代。

典型的垃圾回收算法

Mark-Sweep 标记清除算法

标记然后清除

优点:简单

缺点:容易产生内存碎片

Copying 复制算法

将内存分为两半,每一次将当前部分中被使用的内存复制到另一半,然后将当前这一半内存清空。

优点:简单

缺点:内存消耗大

Mark-Compact 标记整理算法

标记被使用的内存,将他们向一端移动,清除边界以外的内存。

JVM中的垃圾回收算法

目前大部分JVM虚拟机的垃圾回收算法为:在新生代中使用 Copying 算法,在老年代中使用 Mark-Compact 算法

典型的垃圾收集器

Serial/Serial Old 收集器

一个最古老、最基本的垃圾收集器,它是单线程的且在它运行时需要暂停主线程,新生代使用copying,老年代使用Mark-compact

ParNew 收集器

ParNew收集器是Serial收集器的多线程版本

Parallel Scavenge 收集器

多线程收集器,不需要暂停主线程,使用 copying 算法,吞吐量可控

Parallel old 收集器

Parallel Scavenge 收集器的老版本,使用MC算法

CMS 收集器

并发收集器,使用 SW 算法

G1 收集器

并发、并行收集器,取决于机器,能建立可预测的停顿时间模型