JVM的基本认识
文章目录
- 【JavaEE】了解JVM
- 1. JVM中的内存区域划分
- 1.1 JVM的核心区域
- 1.2 JVM内存城防图
- 2. JVM的类加载机制
- 2.1 loading
- 2.2 verification
- 2.3 preparation
- 2.4 resolution
- 2.5 initialization
- 2.6 类加载触发的时机
- 2.7 双亲委派模型
- 3. JVM中的垃圾回收策略
- 3.1 JVM释放的空间
- 3.2 GC的两个阶段
- 3.3 垃圾回收算法
- 3.3.1 引用计数
- 3.3.2 可达性分析
- 3.4 释放“垃圾”对象
- 3.4.1 标记释放
- 3.4.2 复制算法
- 3.4.3 标记整理
- 3.4.4 分代回收
- 4. JMM
JVM是Java虚拟机(Java Virtual Machine)的缩写
- 它是Java编程语言的关键组成部分。
- JVM是一种用于在计算机上执行Java字节码的虚拟机。
- 它是Java平台的核心技术,提供了跨平台的特性和Java的主要优点。
JVM的来历可以追溯到20世纪90年代初,当时Sun Microsystems(现在是Oracle Corporation的一部分)开发了Java语言。
- Java的目标是在不同的硬件和操作系统平台上实现 “一次编写,到处运行” 的理念。
- 为了实现这一目标,Sun Microsystems开发了JVM,它是Java语言的运行时环境。
接下来就是介绍JVM中重要的几个 芝士点
JVM其实就是一个java进程,java进程会从操作系统这里申请一大块内存区域,再进行一系列划分,给java代码使用~
而我们重点要学的是内存的进一步划分,不同的区域有不同的用途
- 堆,java中实例化出来的对象,即成员变量
- 栈,维护方法之间的调用关系,即局部变量
- 方法区(旧说法)/元数据区(新说法),类加载之后的类对象,包括静态变量、方法…
- 类对象就是.class文件的特殊数据结构构成的对象,类名.class去获取
- 方法不是变量,其实在内存中是字节码(二进制指令)形式存在!
经典的面试题:
注意:
Apple a = new Apple();
记住java对象的这个核心关系即可:
灰色:两个栈
- 虚拟机栈,给java代码使用的
- 本地方法栈,是给JVM内存的native本地方法使用的(JVM的本地方法内部是通过C++实现的)
之前的String常量池…也在元数据区里~
- 不必多说~
Program Counter Register(程序计数器)
用途是记录当前程序执行到哪个指令~
- 简单地用long类型的变量去存储 “一个内存地址”
- 这个内存地址就是下一个要执行的字节码所在的地址
CPU也是有这么一个专门的寄存器,JVM参考了CPU
堆的细节安排,在垃圾回收策略里就体现出来了,随后讲解
堆和元数据区,在一个JVM进程中,只有一份;栈和程序计数器,则存在多份(每个线程只有一份)
类加载:把.class文件,加载到内存,得到类对象,这样的过程~
类加载的步骤,其实非常的复杂,而我们只需要理解一些基本流程
提到了 五个词
找到.class文件,并且读文件内容,获取到字节码
.class文件的数据格式:
这一步就是验证这个.class文章的内容是否符合标准,感兴趣的可以去研究一下每个成员的含义~
给类对象分配内存空间
解析,则是针对字符串常量,进行初始化
最主要是将“常量池的符号引用替换为直接引用”:
针对类对象进行初始化(初始化静态成员变量,静态方法,执行静态代码块,加载父类、内部类…)
第四第五步我觉得界限不明显,可能是本就是一步,比较这五个步骤是人为划分出来的
并不是jvm一启动,就把所有的.class都加载了!整体是一个“懒汉模式”,非必要不加载
因为这个好名字,成为了一个热门面试题,其实这个加载步骤在类加载中并不是什么关键的步骤~
接下来就来好好了解一下吧!
主要工作,在第一个步骤中,找.class文件的一个过程~
JVM中,内置了三个类加载器(加载类,需要用到的一组特殊模块)
也可以自己去定义类加载器~
三个类加载器负责三组不同的目录~
当我们具体加载一个类的时候,需要先给定一个类的全限定类名“一系列包名.类名”,例如:“java.lang.String”
双亲委派模型被称为“双亲”,是因为它建立了一个父子关系的类加载器层级结构,通过委派加载的方式提供了一种高效、安全和一致的类加载机制。就体现在两个委派方向咯~其实是翻译的问题,双亲就是family~
JVM中帮助程序员自动释放内存的~
在C中,malloc的内存必须手动free,否则就容易出现内存泄露(只申请不释放,出现逐渐崩溃)
java等后续的编程语言,引入了GC来解决这个问题~
GC的全称是垃圾回收(Garbage Collection)。在计算机科学中,垃圾回收是一种自动化的内存管理技术,用于在程序运行时自动回收不再使用的内存资源,以便重新分配给其他需要的对象。
当程序运行时,会动态地创建和销毁对象。由于对象的动态性,手动管理内存资源变得复杂和容易出错。垃圾回收器(GC)的作用就是在程序运行时监测和识别不再使用的对象,然后释放其占用的内存资源。
能够有效的减少内存泄露的出现概率!作死的一样会出现~
申请内存的时机是明确的,使用到了必须要申请
释放内存的时机是模糊的,完全不使用了才能释放
而C/C++将这个释放的时机,全权交给程序员,但是 java的JVM则通过一系列策略自动判断是否释放
是堆,GC的主要目标
因为栈是局部变量,创建与销毁本就系统自动的行为(随着线程的销毁而销毁,汇编操作中(栈帧创建与销毁,即入栈和出栈)方法结束的出栈操作而被销毁)
程序计数器,就一个long变量,随着线程销毁而销毁
元数据区/方法区,存的类对象,很少会“卸载”,进程结束销毁~
而GC就是以对象为单位进行释放的,即释放内存=释放对象
接下来我们要了解一下垃圾回收算法,我们学习思想,不代表JVM的真实实现方法,JVM的实现方法是在此基础上的优化~
一个对象,后续不再使用,就可以认为是垃圾~
java中使用一个对象,只能通过“引用”~
- 如果一个对象,没有引用指向它,此时这个对象一定是无法被使用的(妥妥的垃圾)
- 如果一个对象,已经不想用了,但是这个引用可能还被指向着(这个携带程序员主观意愿,JVM无法判断)
不针对JVM的判断方法,python和PHP采取了一个算法:引用计数
就是给对象安排一个额外的空间,保存一个整数,表示该对象有几个引用指向它~
缺陷就是:
每个对象都需要怎么一个可见来存放这个计数
还有个漏洞,就是一些“循环引用”引起的问题,最典型的就是循环链表
而这个算法,才是JVM采取的方案,并没有采取引用计数~
边的箭头我省略了
然后我们就寻找其中特殊的起点(蓝),开始遍历,每个蓝色的都便利一边,访问的到的标记为“可达”,所有蓝点都便利完后,未被标记的就是“不可达”
即顺藤摸瓜
缺点:
随着java的发展,JVM的垃圾回收不断的更新优化,STW问题也被很好的应对,不能完全消除,但是STW的时间可以忽略不及了~
接下来介绍三种典型的策略
通过找的过程后,我们已经知道哪些是需要释放的了~
而“标记释放”则是直接将被标记为“不可达”的内存空间直接释放,虽然速率快,但显然,释放出来的空间,七零八落,这导致这些空间释放了之后,完整性不高,甚至可能导致之后无法再被申请!
就相当于,你有2G的内存,但是不连续,都是内存碎片,这样就导致100M的空间都申请不出来~
这种算法是将原本的堆去分为两个部分,一次只用其中一半:
而删除的时候,将“可达”的内存搬运到另一侧
然后这一侧,全部统一释放~
解决了“内存碎片”的问题,但是也很明显
这种算法则是类似顺序表删除元素操作的方式:
解决了“内存碎片”的问题,但是缺点很明显:
搬运开销还是比较大
对于前面三种策略其实各有千秋,但是都有缺点,而我们现在要做的就是,在他们适合存在的场合使用他们,将利益最大化,就衍生出算法“分代回收”,这也联系到“堆”的布局:
这就有一个概念:“年龄”
还有一个普遍的经验规律,或者说是一个代码习惯:
即,“要死的话早就死了”
分代回收过程如下:
贯彻这个过程的一句话就是:“要死的对象早就死了,活下来的就是有两把刷子的”
感兴趣的同学可以去学习“垃圾收集器”,这就是具体的实现方法了,有改进和优化…
- CMS
- G1
- ZGC
认识越新的越好~
JMM的全称是Java内存模型(Java Memory Model)。
- Java内存模型定义了Java程序中多线程并发访问共享内存的行为规范,确保多线程程序的正确性和可预测性。
Java内存模型主要关注的是多线程环境下的共享内存访问问题。
- 在多线程编程中,多个线程同时访问和修改共享的变量和对象可能会导致不可预料的结果,如数据竞争、内存可见性问题等。
- Java内存模型提供了一套规范,定义了一系列规则和原则,来约束线程如何协作和访问共享内存。
Java内存模型包含了对于线程之间的操作顺序、变量的可见性、原子性操作、内存屏障等方面的规范。
- 它确保在满足规定的条件下,程序员可以正确地编写并发程序,而无需担心数据不一致或未定义行为。
通过定义内存访问规则和操作顺序,Java内存模型提供了happens-before关系的概念。
- happens-before关系指定了对于不同线程之间操作的顺序性,确保线程之间的操作按一定的顺序发生,从而保证了程序的正确性。
Java内存模型的规范不仅仅适用于Java语言本身,也适用于运行在Java虚拟机上的其他语言。
- 它为多线程编程提供了标准化的原则和规则,使得程序员能够更加方便地控制线程的行为,编写并发安全的程序。
总结而言,JMM的全称是Java内存模型,它定义了Java程序中多线程并发访问共享内存的行为规范,确保多线程程序的正确性和可预测性,提供了一套规则和原则来约束线程的协作和访问共享内存。
之前在讲多线程线程安全(内存可见性)的时候讲过了,传送门:【JavaEE】线程安全问题_s:103的博客-CSDN博客
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭!总的来说,JVM不需要了解的太深,如果你能理解本篇文章,就足够了~
JavaEE的初阶内容已经结束,接下来将学习JavaEE的进阶内容,比如一些框架,敬请期待!