浅谈Java虚拟机(JVM)

article/2025/8/22 17:08:05

前言👇

        第一次在CSDN上写博客(2022.03.24,大二下),历时×××天,期间因为准备蓝桥杯有所延误。思考了一下,决定在第一篇写一写JVM,不能保证所有的东西都是对的,虚心求教,有不对的地方请大佬们多多指教(doge)。

正文👇

JVM运行流程:

如图:

此图描述了JVM虚拟机运行时的基本流程:①将.java等文件进行“前端编译”之后,获得相应的字节码文件(.class)。②使用相应的类加载器将类加载进虚拟机中。③再根据程序员所编写程序的业务逻辑在运行时数据区中进行操作。④执行引擎将字节码指令解释、编译为相应平台上的本地机器码指令。⑤CPU识别机器码指令进行操作。

★★★类的加载:

类的加载分为加载-->链接-->初始化。

加载:

        在内存中生成这个类的一个java.lang.Class的对象,作为方法区中这个类的数据访问的入口。

链接:

        可以把链接进一步分为验证-->准备-->解析。在验证阶段,虚拟机会对字节码文件的格式、语义、字节码、符号引用进行验证。判断字节码文件是否符合要求和规范(cafa babe),判断字节码是否可以被虚拟机正确地执行和判断在系统中是否存在这个类或方法的符号引用,真正的将符号引用转化成直接引用是发生在解析阶段的(或者在运行时进行动态链接)。在准备阶段,主要执行的是类变量的分配内存、默认初始化(赋默认值)和static&final常量的显式初始化(具体可见初始化↓)。在解析阶段,最主要的是将类、方法和接口的符号引用转化为直接引用。

符号引用:在类加载的时候,可能并不知道所引用的类的地址,因此可以使用一些特定的字面量来表示所引用的类。

直接引用:类、类变量、类方法直接指向方法区。此阶段属于静态链接,是相对于栈帧中的动态链接而言的,两者执行的时期不同,前者发生在类的加载阶段,后者发生在程序执行时期。

初始化: 

        初始化阶段主要进行类变量的显式初始化、执行静态代码块、执行<clinit>。在一个类中存在成员变量,成员变量分为类变量和实例变量,变量可以是基本数据类型(int、char...)或者是引用数据类型(类、数组、接口)。成员变量都放在堆中,基本数据类型会指向方法区的运行时常量池中的字面量,引用数据类型指向堆中的对象。用final关键字修饰的变量叫做常量,常量在编译时期就初始化了,存放在字节码文件的常量池中。

    //类变量static int a=10;//准备(赋默认值0)-->初始化(10)static Integer b=10;//准备阶段(赋默认值null)-->初始化(10)static final int c=10;//编译阶段初始化static final Integer d=10;//编译阶段初始化//实例变量int a1=10;//创建对象时初始化Integer b1=10;//创建对象时初始化final int c1=10;//编译阶段初始化final Integer d1=10;//编译阶段初始化/*创建对象的六个步骤:1.判断这个所属的类是否被加载过2.为对象分配内存空间3.处理并发安全问题4.初始化所分配到的空间5.设置对象头6.*执行<init>,真正创建对象 */

类加载器:

       1.引导类加载器(Bootstrap ClassLoader)

       2.扩展类加载器(Extension ClassLoder)

       3.系统类加载器(System ClassLoder)

       4.自定义类记载器

       他们之间从上到下属于上下级的关系,并不属于继承关系,Bootstrap ClassLoader是最顶层的类加载器,它可以用于加载Java_HOME lib下的jar包和类文件。扩展类加载器可用于加载JAVA_HOME lib/ext 下的jar包和类文件。System ClassLoder在导平常用的jar包和自己创建的类的时候用到,并且自定义类加载器必须继承了这个System ClassLoder之后才能生效。

        能够通过对象.getClassLoader()方法获取到当前对象所属类的类加载器,getParent()方法获取上一级的类加载器。

了解了类加载器的分类之后,我们便可以引出“双亲委派机制”👇

双亲委派机制:

        双亲委派机制是指:受到一个类的加载请求之后,类加载器并不会直接开始加载,而是将这个请求委托给他的上级加载器,一直委托给Bootstrap ClassLoader去加载,如果发现不属于这个加载器的加载范围,则会将这个请求再往下进行委派,一直到找到所属的类加载器为止。

优点:1.避免类被重复加载。  2.保护核心API不被篡改、保护核心类库。(沙箱安全机制)

★★★运行时数据区:

        

如上图是来源于阿里的运行时数据区的内存分配图👆

由此可知运行时数据区包括了堆区、元空间、虚拟机栈、程序计数器、本地方法栈。

程序计数器:

        程序计数器用于记录下一条字节码的指令地址,它是线程私有的,程序计数器中不存在GC(垃圾回收)也不存在OOM(OutOfMomery 内存溢出),每一个线程都会分配一个PC寄存器,如果被作为线程公用,则会出现错乱,比如线程1下一条执行第5行的代码,线程2下一条执行第10行的代码,当线程1的CPU时间片用完轮到线程2后,线程2执行到第15行代码时间片又轮到线程1了,那么线程1下一条就会执行第15行的代码,这明显和我们的需求不一致。所以这样就会出现错乱!

⭐虚拟机栈:

        虚拟机栈是内存中非常重要的部分,堆管存储、栈管运行。

        虚拟机栈中又很多栈帧,一个栈帧对应一个方法,一个栈桢中包含局部变量表操作数栈、动态链接、方法返回地址和一些附加信息。局部变量指的是存在于一个方法内部的变量,所以顾名思义,局部变量表是存放局部变量的集合,它是一个数字数组,32位占一个槽(slot)。根据我的理解,因为在栈中是不可能存放对象的,这里涉及到逃逸分析,如果一个局部变量实在方法内被创建的并且没有被返回或者被其他方法所涉及,就称它没有逃逸,因此可以进行栈上分配并且进行标量替换,所谓的标量,我泛泛地理解为基本数据类型,说到这里就可以得知为什么是一个数字数组,因为基本数据类型都可以理解转化为数字的类型(boolean-->0/非0,char-->Ascii码...)。

        操作数栈可以存放计算的中间结果,变量临时的存储空间。他在程序运行的时候将方法内的每一条字节码指令入栈在需要的时候出栈,完成计算。

        动态链接指将符号引用转化为调用方法的直接引用,不同于在解析阶段的“静态链接”,动态链接发生在程序运行时,指向方法区的运行时常量池中。

        方法返回地址个人理解为该栈帧(方法)运行结束之后应该将结果返回的一个地址。

本地方法栈:

        在本地方法栈中存放的大多是一些C/C++编写的方法,与虚拟机栈类似,该区域也是有OOM无GC的。

  

大致介绍完了线程私有的内存区域,下面就是内存共享的区域了,在内存中也是非常重要的!

堆区:

        在堆中存放的是对象。堆中主要分为新生代、老年代,其实可以把字符串常量池单独提出来讲。新生代与老年代的大小比例为1:2,在新生代中还分为伊甸园区(Eden)、S1区(幸存者一区)、S2区(幸存者2区),他们之间的比例为8:1:1。伊甸园区中的对象大部分是朝生夕死的,因为在新生代中垃圾回收(Miner GC)比较频繁,一些对象可能会被GC回收,一些可能经过GC后会被放入到幸存者区中。伊甸园区中还会有线程私有的区域:TLAB,线程的私有缓存区,可以用于解决线程的并发问题并且提高内存吞吐量。

如果对象被GC后放在了幸存者区,每一次Miner GC都会将对象由原先的S区(From区)转移到另一个S(To区)区,并且分代年龄加一。上文(代码块中)提起过对象创建的步骤,在其中一个步骤中会给对象设置对象头,在对象头中存放了对象的GC分代年龄,一个对象的分代年龄如果达到了15次那就会被放入到老年代中,需要注意的是:幸存者区的GC并不是主动进行的,而是伊甸园区进行一次GC之后他才会进行回收(被动)。

        老年代中的对象基本上是比较稳定的,使用比较频繁的,因此老年代中的Major GC不是很多次。

        问:为什么要分代?答:提高提高GC的性能,加快回收的效率,将使用率低和使用率高的对象分开。频繁回收新生代,较少回收老年代。

       对象存放的流程,重点!!!👇

               图来源于 尚硅谷

 ①对象是否能被放入伊甸园区?直接放:(Minor GC之后能不能放?直接放:去老年代);

 ②Minor GC之后伊甸园区中的一部分对象是否能放入到幸存者区?直接放:去老年代;

 ③进入老年代中后并不是直接可以放。对象是否能放入老年代?直接放:(Major GC后能不能直接放?直接放:报OOM);

元空间:

        元空间和永久代一样都是方法区的具体实现,永久代在jdk1.8被替换为元空间。在1.6字符串常量池在永久代中,1.7被移到了堆区中。

        在元空间中主要存放的是类型信息、运行时常量池、JIT缓存代码、域信息、方法信息。元空间中存在OOM和GC,需要注意的是元空间是利用直接内存的。

        如果在在堆中创建了一个类的实例对象,这个对象的对象头中的类型指针会指向方法区的类元信息(Klass 可见上上图)。在运行时常量池中包含了字面量和符号引用,因此一些基本数据类型的变量会指向运行时常量池,在栈帧中的动态链接也会指向运行时常量池。JIT(即时编译器)也会将一些编译后的本地代码放在方法区中,域信息(Filed)是一种属性,可以是类变量也可以是实例变量,在这里我们不做深究,同时在方法区中一定会包含一些方法信息,这是毋庸置疑的。

        深究“对象”:

         创建对象的六个步骤:
                 1.判断这个所属的类是否被加载过
                 2.为对象分配内存空间
                 3.处理并发安全问题
                 4.初始化所分配到的空间
                 5.设置对象头
                 6.*执行<init>,真正创建对象 

          对象的创建不是一个简单的步骤就足够完成的,即使前5个步骤并不是“真正”的创建对象,但是也非常重要。在最后一个步骤中虚拟机为实例变量显式初始化、执行代码块并且执行<init>方法之后,对象才能算真正的创建了。

          对象也并不是只存放它所表示的实例信息(实例数据)这么简单,他还包含了对象头,对象头中由运行时元数据和类型指针,同时对象中还有“对齐填充”。这也是面试的热点,在对象头中的运行时元数据里包含了很多东西,对象的哈希值、GC分代年龄、锁的状态、线程持有的锁、偏向线程ID、偏向时间戳。这都是构成对象头很重要的部分。

          关于String:

                对于String对象的创建有两个方法:①String str=new String(“abc”);②String str=“abc”;

                追根溯源他们两个是完全不一样的,第一种方法是通过new关键字来创建实例,这种方法会在堆中创建一个String对象的同时在字符串常量池中也添加一个“abc”字面量。而第二种方法则直接让str变量指向字符串常量池中的字面量“abc”,如果常量池中不存在的话会在常量池中创建一个“abc”。

                进阶:常量之间拼接不会在堆中创建对象,变量拼接可能会影响到堆和字符串常量池。

        //常量拼接String str="a"+"bc";final String t1="a";final String t2="bc";String str1=t1+t2;//变量拼接String str2=new String("a")+new String("bc");//1.a 2.bc  3.a 4.bc 5.StringBuilder 6.abcString str3=new String("a")+"bc";//1.a 2.bc   3.StringBuilder 4.a 5.abc

intern()方法:

        intern()方法是将指针指向字符串常量池中指定常量的方法,如果常量池中不存在,则先创建一个再指向。

        String s1_0="bcd";String s1_1=new String("xyz");String s1_2=new String("abc")+new String("xyz");s1_0.intern();//Ⅰs1_1.intern();//Ⅱs1_2.intern();//Ⅲ

Ⅰ:因为已经在常量池中创建了一个“abc”字面量了所以intern()方法会将指针指向字面量“abc”。

Ⅱ:在new的同时也在常量池中创建了“xyz”字面量所以也指向“xyz”。

Ⅲ:在常量池中没有“abcxyz”(不懂的见上文👆)所以要先创建一个字面量然后再指向它。

小结:

            此文简单介绍了一下JVM的内存空间划分、运行流程等相关的知识,之后打算说一下垃圾回收和JVM的性能优化等,有不对的地方还请大家多指教指教,小白在此。谢谢大家的支持!


http://chatgpt.dhexx.cn/article/vyqDNkmX.shtml

相关文章

[JVM] Java虚拟机栈

1. 概念 Java虚拟机栈&#xff08;Java Virtual Machine Stacks&#xff09;是线程私有的&#xff0c;栈使用的内存不需要保证是连续的&#xff0c;栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程&#xff0c;都对应…

【JVM】Java虚拟机简介

【JVM】Java虚拟机简介 我们看中的并非Java语言&#xff0c;而是JVM。——Java之父James Gosling Java 虚拟机&#xff08;Java virtual machine&#xff0c;JVM&#xff09;是运行 Java 程序必不可少的机制。 JVM实现了Java语言最重要的特征&#xff1a;即平台无关性。这是因…

JAVA虚拟机概述

本博客内容为《深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践》的阅读笔记。 1 Java技术体系 仅从传统意义上来看&#xff0c;Sun官方所定义的Java技术体系包括以下几个组成部分&#xff1a; Java程序语言设计各种硬件平台上的Java虚拟机Class文件格式Java API类库来…

详解Java虚拟机

资料来源&#xff1a;尚硅谷宋红康JVM全套教程&#xff08;详解java虚拟机&#xff09;_哔哩哔哩_bilibili 1.JVM与Java体系结构 1.1. 前言 如果我们把核心类库的API比做数学公式的话&#xff0c;那么Java虚拟机的知识就好比公式的推导过程。 计算机系统体系对我们来说越来越远…

java虚拟机到底是什么

转自:http://blog.csdn.net/zhangjg_blog/article/details/20380971 http://blog.hesey.net/2011/04/introduction-to-java-virtual-machine.html 虚拟机是一种抽象化的计算机&#xff0c;通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架…

深入理解Java虚拟机到底是什么

什么是Java虚拟机 作为一个Java程序员&#xff0c;我们每天都在写Java代码&#xff0c;我们写的代码都是在一个叫做Java虚拟机的东西上执行的。但是如果要问什么是虚拟机&#xff0c;恐怕很多人就会模棱两可了。在本文中&#xff0c;我会写下我对虚拟机的理解。因为能力所限&am…

java虚拟机(JVM)

一、虚拟机的组成 虚拟机的组成主要有&#xff1a;方法区、堆都为线程共享区域&#xff0c;有线程安全问题。 栈、本地方法栈、程序计数器为线程的独享区域&#xff0c;不存在线程安全问题。 注&#xff1a;JVM的调优主要是针对堆和栈进行的 1.1&#xff1a;运行时数据区域 …

JVM——Java虚拟机架构

0. 前言 Java虚拟机&#xff08;Java virtualmachine&#xff09;实现了Java语言最重要的特征&#xff1a;即平台无关性。 平台无关性原理&#xff1a;编译后的 Java程序&#xff08;.class文件&#xff09;由 JVM执行。JVM屏蔽了与具体平台相关的信息&#xff0c;使程序可以…

深入理解Java虚拟机

前言 JVM是什么&#xff1f; JVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09;顾名思义就是用来执行 Java 程序的“虚拟主机”&#xff0c;实际的工作是将编译生成的.class 文件&#xff08;字节码&#xff09;翻译成底层操作系统可以运行的机器码并…

一文彻底了解JVM

目录 JVM内存结构 GC算法和收集器 JDK性能调优监控工具 GC调优 JVM内存结构 类加载机制 Java运行时编译源码(.java)成字节码&#xff0c;由jre运行。jre由java虚拟机实现。JVM分析字节码&#xff0c;后解释并执行。 1、全盘负责委托机制 当一个ClassLoader加载一个类的时…

通俗易懂理解JAVA虚拟机

目录 前言 一、什么是JAVA虚拟机&#xff08;JVM&#xff09; 二、内存结构 1.程序计数器 2.虚拟机栈 3.本地方法栈 4.堆 5.方法区&#xff08;元数据区&#xff09; 6、执行引擎 7、直接内存 三、垃圾回收 1.如何判断对象可以回收&#xff0c; 2.垃圾回收算法…

Java虚拟机(JVM)你只要看这一篇就够了!

本文是学习了《深入理解Java虚拟机》之后的总结&#xff0c;主要内容都来自于书中&#xff0c;也有作者的一些理解。一是为了梳理知识点&#xff0c;归纳总结&#xff0c;二是为了分享交流&#xff0c;如有错误之处还望指出。 用XMind画了一张导图&#xff08;源文件对部分节点…

简单的文本编辑器

今天应同学的需求写了一个文本编辑器可以简单的时间文本的打开、删除、显示、查找、插入的简单功能 C语言代码如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <time.h> #define MAX 10000 #include <stri…

使用vue的富文本编辑器操作

使用vue的富文本编辑器操作 vue的富文本编辑器使用以及多图片文件上传与回显 一. vue-quill-edit 1. 安装vue的富文本 cd 当前的vue项目路径 npm install quill --save npm install vue-quill-editor --save2. 在页面中引入vue的富文本 //引入Vue的ueditor的资源 import {q…

Ckeditor富文本编辑器

开发工具与关键技术&#xff1a; MVC 撰写时间&#xff1a;2021/9/28 下面我们讲Ckeditor富文本编辑器的使用&#xff1b; 要使用Ckeditor富文本编辑器&#xff0c;需要在Ckeditor官网中下载js插件&#xff0c;下载后会得到一个ckeditor文件&#xff1a; 其中包含以下文件&am…

[web]前端富文本编辑器

关于富文本编辑器 在HTML中&#xff0c;用于输入文本的只是<input type"text"/>和<textarea>这2种标签&#xff0c;这些标签都只能输入纯文本&#xff0c;不可以对输入的内容进行编排&#xff01; 在实际应用时&#xff0c;例如发布文章&#xff0c;需要…

深入浅出富文本编辑器

‍ ‍大厂技术 坚持周更 精选好文 编辑器介绍 常见的富文本编辑器现实方式可以分成两大类&#xff0c;分别是用 textarea 和 contenteditable 来实现。 textarea 结构简单使用方便&#xff0c;一些文本格式和复杂的样式难以实现&#xff0c;推荐仅在对编辑要求不高的场景使用…

推荐几个非常不错的富文本编辑器

1、wangEditor——基于javascript和css开发的 Web富文本编辑器&#xff0c; 轻量、简洁、界面美观、易用、开源免费。 界面截图&#xff1a;官网地址 2、TinyMCE——TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器&#xff0c;由JavaScript写成。它对IE6和Firefox1.5都有…

vue使用富文本编辑器vue-quill-editor

问题描述&#xff1a; 我们在开发过程中经常会遇到使用富文本编辑器进行操作&#xff0c;当前插件市场上关于富文本的编辑器挺多的&#xff0c;我们就不一一个介绍了&#xff0c;现在我们主要讲解一些vue-quill-editor富文本编辑器的使用操作和注意事项。 效果图 具体操作使用…

LayUI - 富文本编辑器

一个做后端的猿&#xff0c;难免用到LayUI&#xff0c;首先在这里&#xff0c;不推荐使用&#xff0c;坑多 我这里用的是layui-v2.5.7版本 一、富文本编辑器 缺点&#xff1a;功能太少&#xff0c;只能满足简单需求&#xff0c;只有下面这几个功能 废话少说&#xff0c;直接丢…