理解JVM

news2024/11/25 16:22:32

认识JVM

Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。
什么是字节码? 字节码就是jvm能理解的代码。即扩展名为 .class 的文件。
我们日常的java文件先编译成.class 文件 然后在jvm上运行。
图片.png


个人觉得 内存区域是理解JVM相关的基石。所以彻底理解他是非常重要的

图片信息来自JavaGuide
图片.png

线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享的:

  • 方法区
  • 直接内存 (非运行时数据区的一部分)

程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,

注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。

(Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。))

Java 虚拟机栈会出现两种错误:
StackOverFlowError 和 OutOfMemoryError:
StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
OutOfMemoryError: Java 虚拟机栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

Java 栈可以类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。

Java 方法有两种返回方式:
return 语句。
抛出异常。
不管哪种返回方式都会导致栈帧被弹出。

此外还有一个本地方法栈 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。


Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
(之前有一篇讲到 java引用变量如string 引用会存在堆里面 而实际内容会存在栈里面 )

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
(图片来自javaguide)
图片.png

堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:

  1. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded : 当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
  2. java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过-Xmx参数配置,若没有特别配置,将会使用默认值,详见:Default Java 8 max heap size)

方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。


方法区
2.5.1 方法区和永久代的关系

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。

运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。


对象的创建

Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

step2 内存分配

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式“指针碰撞”“空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

Step5:执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。


重点补充内容:

String str1 = "abcd";//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd"";
String str2 = new String("abcd");//堆中创建一个新的对象
String str3 = new String("abcd");//堆中创建一个新的对象
System.out.println(str1==str2);//false
System.out.println(str2==str3);//false

这两种不同的创建方法是有差别的。

第一种方式是在常量池中拿对象;
第二种方式是直接在堆内存空间创建一个新的对象。
记住一点:只要使用 new 方法,便需要创建新的对象。
这张图更方便理解一点
图片.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/534512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务简介,SpringCloud Alibaba Nacos的安装部署与使用,Nacos集成springboot

目录 一.认识微服务 1.0.学习目标 1.1.单体架构 单体架构的优缺点如下&#xff1a; 1.2.分布式架构 分布式架构的优缺点&#xff1a; 1.3.微服务 微服务的架构特征&#xff1a; 1.4.SpringCloud 1.5Nacos注册中心 1.6.总结 二、Nacos基本使用 &#xff08;一&…

【C++】深入剖析C++11新特性

目录 一、C11简介 二、统一的列表初始化 1.&#xff5b;&#xff5d;初始化 2.std::initializer_list 三、声明 1.auto 2.decltype 3.nullptr 四、范围for 五、final和oberride 六、STL中一些变化 1.array 2.forward_list 3.unordered_map和unordered_set 七、右…

RabbitMQ养成记 (2. java操作MQ快速入门,日志监控,消息追踪)

快速入门 刚开始我们就一步一步来&#xff0c; 先搞什么spring集成。 先使用原始的java代码来操作一下MQ。 这里给新手兄弟的建议&#xff0c;这种技术性的学习 一定要先动手&#xff0c;从简单的地方动手&#xff0c;一步一步来&#xff0c;不然上来就搞理论或者复杂的应用很…

JDBC API

注册数据库驱动 Class.forName("com.mysql.jdbc.Driver"); 所谓的注册驱动&#xff0c;就是让JDBC程序加载mysql驱动程序&#xff0c;并管理驱动 驱动程序实现了JDBC API定义的接口以及和数据库服务器交互的功能&#xff0c;加载驱动是为了方便使用这些功能。 获…

Spring IOC相关注解运用——下篇

目录 一、Configuration 二、ComponentScan 1. 说明 2. 测试方法 3. 运行结果 三、PropertySource 1. 说明 2. 测试方法 3. 测试结果 四、Bean 1. 说明 2. 添加驱动依赖 3. 将Connection对象放入Spring容器 3. 测试 五、Import 1. 说明 2. 测试方法 3. 运行结…

从一道go逆向出发,讨论类tea的逆算法

tea代码很短&#xff0c;经常被直接复制为源码&#xff08;而不是像标准算法那样调库&#xff09;。在ctf逆向中也算比较常见&#xff0c;复杂度适中。 例题是一道go逆向&#xff0c;经go parser处理后&#xff0c;核心代码如下图。 panic算是go的专有名词&#xff0c;类似异常…

吃透 Spring AOP (1.理解概念)

理解 什么是AOP AOP&#xff0c;全称面向切面编程。 它可以说是对面向对象OOP的思想升华。从总的理解来讲&#xff0c;AOP是横向对不同程序的抽象。这个思想要不断实践动手之后&#xff0c;才会有很深刻的理解 理解 代理模式 在理解AOP之前&#xff0c;我们首先要单独说一个…

FFMPEG录屏(16)--- MAG(Magnification)捕获桌面

最近增加了对Magnification API捕获桌面的支持&#xff0c;记录一下过程和其中遇到的问题。 参考资料 Magnification API overview Magnification API sample webrtc screen_capturer_win_magnifier.cc Structured Exception Handling (C/C) 前言 我又不得不吐槽一下了&a…

【JavaWeb】-- HTTP、Tomcat、Servlet

文章目录 HTTP1.简介2.请求数据格式2.2.1 格式介绍 3.响应数据格式3.1 格式介绍3.2 响应状态码 Tomcat1.简介1.1 什么是Web服务器 2.基本使用2.1 安装2.2 启动2.3 关闭 3.Maven创建Web项目4.IDEA使用Tomcat4.1集成本地Tomcat4.2 Tomcat Maven插件 Servlet1.简介2.快速入门3.执行…

okhttp篇3:RealCall

Call Call一般代表一个已经准备好的Request&#xff0c;Request的包装类&#xff0c;可执行&#xff0c;它一般有两个主要的方法&#xff1a; execute(立即执行&#xff0c;并阻塞线程&#xff0c;直到Response返回)enqueue(将Request放入队列&#xff0c;等待线程池调度执行…

spring源码学习_01 本地环境搭建

参考网上各种资源&#xff0c;终于把spring源码运行起来了&#xff1b;步骤总结如下&#xff1a; spring版本&#xff1a; 5.2.x 本地系统mac idea 2019.3.2 下载地址&#xff1a;https://www.jetbrains.com/idea/download/other.html jdk 11 下载地址&#xff1a;https://repo…

【设计模式二十三剑】✨编写更高质量代码的秘诀✨

文章目录 ✨✨简述&#x1f386;【万能之门】简单工厂模式&#xff08;Simple Factory&#xff09;&#x1f387;【制造之剑】工厂方法模式&#xff08;Factory Method&#xff09;&#x1f387;【生产之剑】抽象工厂模式&#xff08;Abstract Factory&#xff09;&#x1f387…

C语言基础知识:函数的调用

函数的定义 一般来说&#xff0c;执行源程序就是执行主函数main&#xff0c;其他函数只能被主函数所调用&#xff0c;而其他函数之间也可以相互调用。 1.标准库函数&#xff1a; 分为&#xff1a;I/O函数&#xff0c;字符串&#xff0c;字符处理函数&#xff0c;数学函数&am…

Go语言的函数和defer用法

目录 函数的基本用法 函数中的变长参数 递归函数(recursion) 函数是“一等公民” 函数中defer的用法 defer的底层原理 使用 defer 跟踪函数的执行过程 defer的注意事项 &#xff08;1&#xff09;Go语言内置的函数中哪些可以作为deferred 函数 &#xff08;2&#xf…

面试code(1)—— 排序算法

算法动画 从小到大排序 1 冒泡排序 被动的将最大值送到最右边 1、比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。 2、对每一对相邻元素作同样的工作&#xff0c;从开始第一对到结尾的最后一对。这步做完后&#xff0c;最后的元素会是最大的数。 3、针对…

Redis修炼 (17. redis主从集群的数据同步原理 全量同步/增量同步)

数据同步 在一个集群之中 数据同步是很重要的。 redis的集群有多种。 我们这次主要说 主从集群。 全量同步 既然是主从集群 那么肯定是 1个master节点 多个从节点。redis的集群中的第一次同步 我们叫 全量同步&#xff1a; 为什么要强调第一次&#xff1f; 因为如果你是第一…

独家策略大放送:最高年化150%的策略,谁不感兴趣?(含免费版)

上一节我们在沪深300中回测了550中均线交叉策略,有朋友想看看这些策略在沪深300以外的中小市场表现如何,同时大家都非常好奇表现抢眼的老Q自研指标WMA_Q系列到底是怎么计算的。 于是老Q又选择了中证500和创业板指来验证下这些策略是否能有同样的表现(PART 1),顺便在今天的…

流水线中的握手信号笔记

1.《握手信号的打拍(一)》 解释了&#xff0c;为什么在流水线中&#xff0c;握手信号不能简单得加一级寄存器 业界关于流水线级握手信号的标准答案是 skid buffer&#xff0c;此外还有人提到了 Register slice 2.《握手信号的打拍(二)》 为什么简单加一级寄存器会握手失败 &…

抖音SEO系统源码开发搭建/MVC二次开发定制

首先&#xff0c;抖音SEO矩阵系统源码开发&#xff0c;如何做独立部署&#xff0c;首先我们需要深刻理解这个系统的开发逻辑是什么&#xff1f;开发的前言是在抖音平台做流量新增&#xff0c;现在抖音及各大主流短视频平台&#xff0c;流量新增&#xff0c;各大企业需要在短视频…

Redis BigKey

Redis BigKey 一 面试题引入二 MoreKey案例2.1 大批量往redis里面插入2000W测试数据key2.1.1 Linux Bash下执行&#xff0c;插入100W2.1.2 通过redis提供的管道 --pipe命令插入100W大批量数据 2.2 key *2.3 生产上如何限制keys*/flushdb/flushall等危险命令以防止误删误用&…