Java虚拟机 --- JVM

x33g5p2x  于2022-05-11 转载在 Java  
字(2.9k)|赞(0)|评价(0)|浏览(287)

1. 什么是JVM

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机.
虚拟机是指通过软件模拟的具有完整硬件功能的,运行在一个完全隔离的环境中的完整计算机系统.

2. JVM 内存区域划分

堆 : 放的是 new 的对象
方法区 : 放的是 类对象
栈 : 放的是局部变量
程序计数器 : 放了一个内存地址
注意事项:

  1. 堆 和 方法区 只有唯一的一份
  2. 栈和程序计数器是每个线程都有一个\
  3. 堆溢出,new的对象太多
  4. 栈溢出,无限递归

示例 :

class Student{
    public int Id;
}
public class Test {
    Student student2 = new Student();
    static Student student3 = new Student();
    public static void main(String[] args) {
        Student student1 = new Student();
    }
}

student1 是在栈里
student2 是在堆里
student3 是在方法区里

3. JVM 类加载机制

3.1 类加载过程

这是类加载的过程.

① 加载

Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

② 验证

验证刚刚的Class文件是不是一个合法的文件.
验证选项:

  1. 文件格式验证
  2. 字节码验证
  3. 符号引用验证…

③ 准备

为了类对象中的一些成员分配内存空间(静态变量),并且进行一个初步的初始化(把初始的空间设为全0).
public static int value = 333
它初始化 value 的值为0,并非333

④ 解析

Class文件中就会涉及一些字符串常量.
解析就是把这个类加载过程中,需要把这些字符串常量给替换成当前JVM内部已经持有的字符串常量的地址.

⑤ 初始化

这个阶段就是真正开始堆静态变量进行初始化.同时也会执行static代码块

3.2 双亲委派模型

在 JVM 中, 有三个类加载器(三个特殊的对象) 来负责进行这里的找文件的操作.

这里如果一个类收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求给委派给父亲加载器.
BootStrap 是爷爷,ExtClassLoader 是父亲,AppClassLoader 是儿子.
当收到一个类加载请求,首先从 AppClassLoader 开始查找,在查找之前会把这个请求委派给父亲ExtClassLoader,在 ExtClassLoader 开始查找之前,会把这个请求委派给父亲 BootStrap.BootStrap 没有父亲,就自己查找.
 
如果BootStrap找到了,就直接加载.如果没有找到,就回到孩子继续查找.
如果ExtClassLoader找到了,就直接加载.如果没有找到,就回到孩子继续查找
如果AppClassLoader找到了,就直接加载.如果没有找到,就抛出 ClassNotFoundException.

4. JVM 垃圾回收

4.1 什么是垃圾回收

垃圾回收(GarbageCollection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。

4.2 Java 的垃圾回收要回收的内存是哪些

由于栈和程序计数器的内存都是和具体的线程绑定在一起,代码块结束/线程结束,内存就自动释放了.
方法区和堆是垃圾回收机制要释放的内容.方法区进行垃圾回收比较特殊不考虑.
这里讨论的垃圾回收主要指的是上内存的回收.

4.3 回收堆上的内存,具体是回收什么

堆上的内存分为三类:

  1. 完全要使用
  2. 完全不使用
  3. 一半要使用, 一半不使用

Java 中的垃圾回收, 是以"对象"为基本单位的.一个对象不会存在被回收一半的情况.

4.4 垃圾回收是怎么回收的

垃圾回收就是 先找到垃圾,然后再回收垃圾
垃圾: 就是再也不会被使用到的对象

4.5 判断垃圾的算法

a) 引用计数算法

就相当于在对象里面增加一个引用计数器,
每当有一个地方引用它的时候,计数器就+1.
每当这个引用失效时,计数器就-1

优点 :
规则简单,实现方便,比较高效
缺点 :

  1. 空间利用率比较低(浪费空间)
    如果你一个对象很大,在程序中数目不多.此时引用计数就没有什么影响(本来一个对象就很大)
    如果你一个对象很小,在程序中数目很多,此时引用计数空间开销就很大(本来一个和对象很小,每个对象加个int)
  2. 存在循环引用的问题

b) 可达性分析算法

从一组初始的位置出发, 向下进行深度遍历, 把所有能够访问到的对象都标记为 “可达”.对应的, 不可达的对象就是垃圾.

JVM 中采取的方案. JVM 存在一个/一组线程,周期性的进行遍历,不断的找出这些不可达的对象,由 JVM 回收.

把可达性分析, 初始位置为 “GC Root”
可作为GC Roots的对象包含下面几种:

  1. 栈上的局部变量中的引用
  2. 常量池里面的引用执行的对象
  3. 方法区中, 引用类型的静态成员变量

Java的引用也可以被判定对象的生死了
四种引用:

  1. 强引用: 既能访问对象, 也能决定对象的生死
  2. 软引用: 能访问对象,但是只能一定程度的决定对象的生死
  3. 弱引用: 能访问对象,不能决定对象的生死
  4. 虚引用: 既不能找到对象, 也不能决定对象的生死.只能在对象临被回收前, 进行一些善后工作.

4.6 垃圾回收算法

① 标记 - 清除 算法

该算法分为 “标记” 和 “清除” 两个阶段:首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象.

缺点 :

  1. 效率问题: 标记和清除两个过程的效率都不高
  2. 空间问题: 由于回收后产生的大量的不连续的内存空间,想要申请足够的连续内存,就可能分配失败.

② 复制算法

将内存按容量划分为大小相等的两块, 每次只使用一块.当这块内存需要进行垃圾回收时,会将此区域还存活的对象复制到另一块上.

缺点 :

  1. 空间问题: 可用的内存空间只有一半
  2. 效率问题: 如果要回收的对象比较少,复制的开销就很大

③ 标记 - 整理 算法

跟标记-清除一样,只不过后续让存活的对象都向一段搬运

缺点 :
在这个搬运过程中,也是一个很大的开销,这个开销比复制算法里面复制对象的开销甚至更大

④ 分代算法

在 JVM 中, 进行垃圾回收扫描也是周期性的,这个对象每次经历了一个扫描周期, 就认为"长了一岁"
根据这个对象的年龄来堆整个内存进行分类.年龄短的对象放在一起,年龄长的对象放在一起.不同的年龄的对象,就可以采取不同的垃圾回收算法来进行处理了.

分代回收的过程:

  1. 一个新的对象, 诞生于新生代.
  2. 如果活到了一岁的对象(经历了一轮GC),就拷贝到生存区
  3. 在生存区中,对象也要经历若干轮GC.每一轮GC逃过的对象,都通过复制算法拷贝到另外的生存区里.这里的对象来回拷贝,每一轮都会淘汰掉一波对象
  4. 在生存区中,熬过一定轮次的GC之后(一般情况默认15次),这个对象仍然没有被淘汰,JVM就认为这个对象未来还会更持久的存在下去.于是就拷贝到老年代去
  5. 进入老年代的对象,JVM都认为是属于能持久存在的对象,这些对象也需要使用GC来扫描,但是扫描的频次就阿达降低了.老年代通常使用的是标记-整理算法.

特殊情况,如果这个对象特别大,就会直接进入老年代.

相关文章