JavaEE|JVM

JVM 简介

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。 虚拟机是指通过软件模拟的具有完整硬

件功能的、运行在一个完全隔离的环境中的完整计算机系统。

JVM三个主要方面

1.JVM内存区域划分

为什么要进行区域划分?

Java 虚拟机是仿照真实的机器和真实的操作系统进行设计,真实的操作系统中, 对于进程的地址空

间是进行了分区域的设计,JVM 也就仿照了操作系统的情况,也进行了分区域的设计。

JVM具体是怎样划分的?

核心区域四个

(1)程序计数器

这是一个很小的区域,只是用来记录当前指令执行到哪个地址了

(2)元数据区

保存当前类被加载好的数据

.java = > .class = > 加载到内存中去

要想运行代码,就需要把.class加载到内存里面(类加载)

(3)栈

用来保存方法的调用关系。每次调用方法就会进入方法内部执行,当方法执行完毕,返回到调用位

置继续往后走

当调用test1方法,进入test1的方法栈

栈这个空间不算大,一般是几MB几十MB这样的情况,大部分情况是够用的,少数情况下可能会出

现"栈溢出"的情况

(4)堆

用来保存new的对象的

java 复制代码
Test test=new Test();

对于t这样的一个对象

如果t是一个局部变量,t就是在栈上

如果t是一个成员变量,t就是在堆上

如果t是一个静态成员变量,t就是在元数据区

堆是JVM中最大的空间区域,往集合类里面添加元素,如果对上的对象不再使用的话就需要释放掉

(垃圾回收)

补充

在Java 8之前,元数据区也叫做"方法区"。

这里的元信息指的就是一些属性,比如,这类叫啥名字,是不是public,继承自哪些类,实现哪些

接口,方法叫啥名字,参数相关信息...

2.JVM类加载

类加载本身是一个复杂的事情

类加载主要关心两个方面

类加载的步骤

五个阶段

a)加载:找到 .class 文件

根据类的全限定名(包名+类名,形如java.lang.String)

打开文件,读取文件内容到内存里(类似于类加载的过程,.class = > 类对象)

b)验证:解析,校验 .class文件读到的内容是否合法的,并且把这里的内容转换成结构化的数据

c)准备:给类对象申请内存空间,此处申请的内存空间相当于是"全0"的空间

d)解析:针对字符串常量进行初始化

字符串常量本身就包含在 .class文件中,就需要 .class文件里解析出来的字符串常量放到内存空间

里(元数据区,常量池中)

d)初始化:针对刚才提到的类对象进行最终的初始化

针对类对象的各种属性进行填充,包括类中的静态成员,如果这个类还有父类,并且父类还没加

载,此环节也会触发父类的加载

类加载的触发时机

并不是Java程序一启动就会加载到所有的类,而是懒汉模式,当Java代码用到哪个类就会触发哪

个类的加载

  1. 构造这个类的实例
  2. 调用/使用类静态属性/静态方法
  3. 使用某个类的时候,如果父类还没有加载也会触发父类的加载

类加载器

类加载器:JVM 中有专门的模块,专门负责类加载,JVM 默认提供了三种类加载器

  1. BootstrapClassLoader 爷 -> Java 标准库的目录
  2. ExtensionClassLoader 爹 -> Java 扩展库的目录
  3. ApplicationClassLoader 子 -> Java 的第三方库 / 当前项目

这是个类加载器首当其冲的就是要进行"找 .class文件"环节,这里的类加载器是通过parent这样的

引用来向上引用

双亲委派模型

双亲委派模型描述了类加载中根据全限定名找到 .class文件的过程,双亲委派模型的实现离不开类

加载器

双亲委派模型工作的过程

1.进行类加载时,通过全限定类名找 .class 的时候,就会从ApplicationClassLoader作为入口开始,然后把加载类这样的任务委托给父亲ExtensionClassLoader来进行

2.ExtensionClassLoader也不会立即进行查找,而是也委托给父亲BootstrapClassLoader来进行,BootstrapClassLoader也想委托给父亲,由于没有父亲只能自己进行类加载。

3.根据类名,找标准库范围是否存在匹配的 .class 文件,如果BootstrapClassLoader没有找到,再把任务还给孩子ExtensionClassLoader

4.接下来 ExtensionClassLoader来负责进行找 .class 文件的过程,找到就加载,没找到也就把任务还给孩子 ApplicationClassLoader

5.接下来 ApplicationClassLoader 负责找 .class. 找到就加载,没找到就抛出异常

垃圾回收

Java中释放内存的手段。在C语言中,申请内存要使用malloc,申请之后一定要手动调用free进行

释放,否侧会出现内存泄漏。但可能因为一些原因导致free没有执行到。手动释放内存太麻烦太容

易出错了,Java 引入垃圾回收进行自动释放,JVM 会自动识别出某个内存是不是后续不再使用了

并且自动释放。

GC的工作区域

GC主要回收JVM中堆的内存区域

|-------|--------------------|
| 程序计数器 | 线程销毁,自然就释放 |
| 栈 | 方法执行结束,栈帧就结束,随之释放了 |
| 元数据区 | 类对象,一般不会释放 |
| 堆 | 创建很多新对象,也会有旧对象消亡 |

说是"内存回收",本质上是"回收对象",不会出现把一个对象是放到一半的情况

GC的工作过程

1.找到垃圾(不再使用的对象)

有两个方法

(1)引用计数(Python,PHP采用了这个方案)

每个对象在new的时候都搭配一个小的内存空间,保存一个整数

这个整数就表示当前对象有多少个引用指向它,每次进行引用赋值的时候都会自动触发引用计数的

修改,通过引用计数记录有多少个引用.,在 Java 中,要想使用某个对象一定是通过引用来完成

的,如果引用计数为 0 了,就说明没有引用指向这个对象了,这个对象就是垃圾

这种方法存在以下几个问题:

1)内存消耗更多

尤其是对象本身比较小,引用计数消耗的空间的比例就更大,假设引用计数是4个字节,对象本身

是8个字节,引用计数相当于提高了50%的空间占用率

2)可能会出现"循环引用"的问题

此时这两对象的引用不为0,虽然不为0但是这两对象都无法使用

(2)可达性分析(Java采取了这个方案)
工作流程
  1. 以代码中的一些特定对象,作为遍历的"起点" => GCRoots

    1. 栈上的局部变量(引用类型)
    2. 常量池引用指向的对象
    3. 静态成员(引用类型)
      这三个对象,程序运行到任何一个时刻JVM 都是容易获取到
  2. 尽可能的进行遍历,为了判定某个对象是否能访问到

  3. 每次访问到一个对象,都会把这个对象标记成"可达",当完成所有的对象的遍历之后,未被标记成"可达"的对象就是"不可达"

JVM 中一共有多少个对象,JVM 自身是知道的,通过可达性分析,知道了哪些是"可达"的剩下的

就是"不可达",不可达接下来要回收的垃圾

可达性分析这个过程是"周期性",每隔一定的时间触发一次这样的可达性分析的遍历

引用计数,是有空间开销;可达性分析,用时间换空间。

2.回收垃圾

(1)标记-清除

把垃圾对象的内存直接释放,但这样会产生内存碎片问题。

如果当内存碎片非常多时,即使总的空闲时间很大,但申请一个稍微大一点的内存都会失败

(2)复制算法

一次只使用其中的一半,把不是垃圾的对象拷贝到另外一侧,然后在把这一侧给整体的释放掉.,

此时可以确保空闲的内存就都是连续的了

缺点:

  1. 内存的空间利用率是很低的
  2. 一旦不是垃圾的对象较多, 复制的成本就会很高 (尤其是这样的对象中包含大的对象的时候)
(3)标记-整理

类似于顺序表的"搬运"

优点:解决内存碎片,同时也能保证内存利用率

缺点:还是存在内存搬运数据的操作,开销挺大的,复制成本的问题仍然存在

(4)分代回收

这里的"代"表示对象的年龄,针对不同的年龄的对象采取不同的策略,不同年龄的对象特点是不同

的。不同年龄的对象特点是不同的,根据经验而言:如果某个对象已经是一个年龄大的对象了,此

时大概率还会继续存在很久;如果某个对象的年龄很小,这个对象可能会很快挂掉

对于这种情况,老年代的GC频次就可以降低;新生代的GC频次就会比较高

具体分配区如下

新创建的对象就放到"伊甸区",绝大部分的伊甸区的对象活不过第一轮 GC 所以幸存区比伊甸区小

伊甸区 => 幸存区:复制算法,复制的对象规模是很少并且复制的开销可控的

幸存区中的对象也要经历 GC 的扫描,每一轮GC都会消灭一大部分对象,剩余的对象再次通过复

制算法复制到另外一个幸存区,如果这个对象在幸存区中经历了多次复制都存活下来了, 对象的年

龄就大了

总体流程图如下

相关推荐
Mahir081 小时前
Spring Boot 自动装配深度解密:从原理到自定义 Starter 实战
java·spring boot·后端·自动装配·自定义starter·大厂面试题
淘源码d1 小时前
产科系统源码,数字产科源码,Java(后端) + Vue + ElementUI(前端) + MySQL(数据库),确保系统稳定性与扩展性。
java·源码·数字产科·产科系统·智能化孕产服务·高危五色预警·智慧产科
wand codemonkey2 小时前
SpringbootWeb【入门】+MySQL【安装】+【DataDrip安装 】+【连接MySQL】
java·mysql·mybatis
Mahir0810 小时前
Spring 循环依赖深度解密:从问题本质到三级缓存源码级解析
java·后端·spring·缓存·面试·循环依赖·三级缓存
RyFit11 小时前
SpringAI 常见问题及解决方案大全
java·ai
石山代码11 小时前
C++ 内存分区 堆区
java·开发语言·c++
绝知此事11 小时前
【算法突围 01】线性结构与哈希表:后端开发的收纳术
java·数据结构·算法·面试·jdk·散列表
无风听海11 小时前
C# 隐式转换深度解析
java·开发语言·c#
一只大袋鼠12 小时前
Git 进阶(二):分支管理、暂存栈、远程仓库与多人协作
java·开发语言·git