【JavaEE】(9) JVM

一、什么是 JVM

JVM(Java 虚拟机)是 JRE(Java 运行时环境)的核心模块。其实称为虚拟机有夸大的嫌疑,因为虚拟机是用软件模拟计算机硬件构造出一个虚拟的电脑,甚至能在上面安装操作系统。而 JVM 只是能将编译好的 .class 文件翻译成 CPU 能识别的指令 ,所以用 Java 运行时称呼更准确。这种机制让 Java 比起 C、C++、Go 效率更低 ,因为后者只需编译一次代码就能得到 CPU 能识别的指令。但是能够跨平台:不同操作系统提供的 api 不同,需要改写 api 调用的逻辑、不同 CPU 的架构不同,编译出来的 CPU 指令不同(现在常见的 CPU 架构:电脑的 x86;手机的 ARM)。基于上面两点,C++ 就很难做到跨平台。而 Java 提供了各种版本的 JVM,比如:Windows x86、Windows arm、Linux x86、Linux arm......程序员只需要写一套代码,下载对应版本的 JVM 就能兼容所有环境。

运行一个 Java 程序的过程:

  • 编写代码,一个 .java 文件,对应了多个类。
  • 使用 javac 命令行工具,将 .java 文件编译成 .class 二进制的字节码文件,一个 .class 对应一个类。
  • 使用 java 命令行工具,运行指定路径下的.class 文件,对应了一个 JVM 的进程 ,其任务就是将 .class 文件的内容翻译执行

二、JVM 的内存区域划分

Java 程序运行,得到进程 ,需要分配内存空间不同区域有不同的功能

1、程序计数器

空间小,用于存储字节码文件中下一条执行指令的地址 。CPU 指令默认是顺序执行,但 while、if、抛出异常等语句需要跳转,所以需要存储该地址。而地址指向的内容,就存放在元数据区。每个线程的程序计数器不同,因为线程的调度,需要保护上下文,因此要分别保存地址(线程私有)。

2、元数据区

在 Java 8 前称为方法区。存储类对象的结构信息 (字节码文件通过二进制描述了该内容):类的名字、类的父类、类实现的接口、类的静态成员(名称、类型、修饰词)、类的方法(名称、参数列表、返回值、修饰词)、常量。Java 反射机制通过 类名.class 获取类对象。(线程共享

3、栈

存储方法的调用关系 ,保证在方法执行结束后,返回上一层调用位置继续执行。栈中的每个元素都是一个栈帧 ,描述了方法的参数、方法的局部变量、方法的返回值、方法返回到的地址。从调试信息可以看到,每个线程都有一个栈(线程私有 ),部分线程栈的信息无法查看,是因为这部分是本地方法栈 ,就是用 C++ 代码实现的 JVM 的内容。我们能打断点查看的是 JVM 栈

4、堆

存放 new 出来的对象非静态成员 (实例属性)、对象头 (和对象相关的信息,如加锁,锁就是对象)。常量以前放在元数据区,后来放到了堆里。

内存空间最大的就是堆;栈比较小,具体看线程个数、线程中方法调用层次;元数据区中等,具体看类、类属性、方法多不多。程序计数器最小,固定 8 字节。

三、JVM 的类加载机制

理解性记忆

把类从硬盘中的 .class 文件里加载到内存中构造为类对象的过程 。具体步骤官方有描述,版本一,三步。版本二,五步(把版本一第二步拆成了三步)。类加载的过程属于懒汉模式第一次用的时候才加载:new 某个类的实例;调用某个类的静态成员、方法;使用某个类的子类。

1、加载

  • 根据代码中编写的"全限定类名"(报名+类名)到 .class 文件。

查找方法涉及到"双亲委派模型":

这个命名不准确,单亲委派模型更恰当。JVM 中有一个类加载器模块,里面有三种类加载器:

1)Bootstrap ClassLoader:负责在 Java 标准库查找。

2)Extension ClassLoader: 负责在 Java 扩展库查找(JDK 厂商自行扩展的功能)。

3)Application ClassLoader: 负责在 Java 第三方库和当前项目中查找。

每个类加载器存储了一个类似指针的 parent 属性,用于标记父亲是谁:

假设加载一个 com.sogou.Test 类(自定义的):类加载器调用入口》A 加载器》E 加载器》B 加载器》B 没查找到》E 没查找到》A 查找到了。如果还没找到,就会抛出异常。其实也可以不经过从下往上,直接从上往下走,只不过 JVM 是这样实现的。

目的:确保三个类加载器的优先级,Bootstrap ClassLoader 最高。当自定义全限定类名与标准库的类名冲突,优先使用标准库的类。

  • 打开文件,读取内容到内存。
  • 按数据格式解析文件。

JVM 规范官方文档:Java SE Specifications,找比较稳定的版本。查看字节码文件格式。

读取 u4,读取 4 字节作为 magic......

2、验证

校验 按照上述格式解析出来的内容是否合法

3、准备

给类对象申请内存空间 ,该空间未初始化,全为 0(自动设为 0 了,不同于 C++ 是随机值,有性能代价)。

4、解析

对代码中的常量进行初始化。将 .class 中的常量值加载到内存

5、初始化

加载类的静态成员的值 ,并执行静态代码块 ,加载父类、接口。(加载过了不重复加,没加就加)

区别于类加载,局部变量在用的时候分配空间和初始化;实例、非静态成员在 new 对象的时候。

四、JVM 的垃圾回收机制

1、什么是 GC

GC 就是垃圾回收机制,减少程序员的工作量,避免内存泄漏自动回收不再使用的内存空间。像 C/C++ 追求高性能,没有 GC 机制(GC 时,其它的线程暂停工作,导致 STW(stop the world)问题),动态申请内存空间后需要手动释放,在释放前可能会有 return、抛出异常的逻辑,导致最后没有释放,造成内存泄漏。

2、GC 释放哪个区域的内存

对象为单位 释放区域的内存空间。为什么以对象为单位:如果以字节为单位,则要区分每个对象的哪些字节是不用的,实现过程更加麻烦。

3、如何回收

3.1、找出哪些对象是垃圾

没用用的对象是垃圾,即没用引用指向的对象。两个算法:

(1)引用计数【Java 没用】

在每个对象里,有一块区域存放被引用的个数,引用一次增1,引用去掉减1,当引用个数为 0 则释放

但这种方法存在2个问题:

  • 内存空间浪费:有一部分空间是计数器,如果对象本身就很小,内存空间有效利用率就很低。
  • 循环引用问题:无法释放循环引用的对象的内存空间。

此时没有任何引用指向这两个对象,但因为引用计数器不为 0,无法被释放。

Python、PHP 等使用该策略,但搭配了其它识别循环引用的算法。

(2)可达性分析【Java 使用】

从一些特殊的引用(未在堆上的引用:栈上的局部变量、元数据区的非静态成员、常量池里的常量)作为 GC roots 开始扫描,能达到的对象标记为"可到达",然后 JVM 获取包含所有对象的列表,去掉"可到达"对象,身下的就是垃圾对象。

此时 c 没有引用不可到达,导致 f 也不可到达。

这种策略没有使用多余的空间,没不存在循环引用问题,但扫描会消耗更多时间。

3.2、释放垃圾对象的内存

内存回收,涉及几种算法:

(1)标记-清除

标记:可达性分析。

清除:直接释放垃圾对象。

问题内存碎片化,可用内存不连续,无法申请较大的内存空间。

(2)复制算法

复制:将内存空间一分为 2,一边使用,一边不使用。假设左边放了很多对象,将左边有用的对象移到右边,左边全部释放。

问题解决了内存碎片化 ,但浪费了大量空间 ,并且复制的开销很大(有用对象很多/很大时)。

(3)标记-整理

整理:类似顺序表搬运,将后面有用的对象按顺序移到前后,后面的空间全部释放。

问题 :解决了碎片化,没有浪费空间,但复制开销仍然很大

(4)分代回收(综合版)

分区:年轻代(包含一个伊甸区,存放新生;两幸存区,同一时刻只用一个)、老年代,认为老年代更不可能是垃圾(要死早死了)。

按分区特点执行不同扫描频率:可达性分析要不断扫描。老年代更不可能是垃圾,扫描频次更低(major GC);年轻代频次更高(minor GC)。

转移分区的过程 :新的对象放入伊甸区,经过一次扫描将可达对象放入幸存区。经过多轮扫描,剩余可达对象不断在两个幸存区 之间转移(复制算法 )。最后扫描到一定轮次(一般是 15 轮),进入老年区。老年区 的垃圾回收机制是标记-清除/标记-整理

特殊情况:对于占内存很大的对象,不适合复制,直接进入老年区。

相关推荐
SugarFreeOixi8 分钟前
Idea打包可执行jar,MANIFEST.MF文件没有Main-Class属性:找不到或无法加载主类
java·jar
Mr Aokey10 分钟前
从BaseMapper到LambdaWrapper:MyBatis-Plus的封神之路
java·eclipse·mybatis
小白学大数据13 分钟前
Java爬虫性能优化:多线程抓取JSP动态数据实践
java·大数据·爬虫·性能优化
yuqifang27 分钟前
写一个简单的Java示例
java·后端
用户20187928316735 分钟前
限定参数范围的注解之 "咖啡店定价" 的故事
android·java
泽虞43 分钟前
C语言深度语法掌握笔记:底层机制,高级概念
java·c语言·笔记
视觉CG1 小时前
【JS】扁平树数据转为树结构
android·java·javascript
哈基米喜欢哈哈哈1 小时前
Netty入门(二)——网络传输
java·开发语言·网络·后端
老虎06271 小时前
Java基础面试题(1)—Java优势(JVM,JRE,JIT,Java类,方法)
java·开发语言·jvm
C182981825752 小时前
类内部方法调用,自注入避免AOP失效
java·开发语言