JVM的小知识总结

加载时jvm做了这三件事:
1)通过一个类的全限定名来获取该类的二进制字节流

什么是全限定类名?

就是类名全称,带包路径的用点隔开,例如: java.lang.String。
即全限定名 = 包名+类型

非限定类名也叫短名,就是我们平时说的类名,不带包的,例如:String
2)将这个字节流的静态存储结构转化为方法区运行时数据结构
3)在内存堆中生成一个代表该类的java.lang.Class对象,作为该类数据的访问入口

2.验证

验证、准备、解析这三步可以看做是一个连接的过程,将类的字节码连接到JVM的运行状态之中
验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会威胁到jvm的安全
验证主要包括以下几个方面的验证:
1)文件格式的验证,验证字节流是否符合Class文件的规范,是否能被当前版本的虚拟机处理
2)元数据验证,对字节码描述的信息进行语义分析,确保符合java语言规范
3)字节码验证 通过数据流和控制流分析,确定语义是合法的,符合逻辑的
4)符号引用验证 这个校验在解析阶段发生

3.准备
为类的静态变量分配内存,初始化为系统的初始值。对于final static修饰的变量,直接赋值为用户的定
义值。如下面的例子:这里在准备阶段过后的初始值为0,而不是7:


4.解析
解析是将常量池内的符号引用转为直接引用(如物理内存地址指针)


5.初始化
到了初始化阶段,jvm才真正开始执行类中定义的java代码
1)初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集
类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
2)当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。
3)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

流程图

小插曲:猜猜这东西执行结果是什么?


public class ParentClass {private int parentX;public ParentClass() {setX(100);}public void setX(int x) {parentX = x;}
}public class ChildClass extends ParentClass{private int childX = 1;public ChildClass() {}@Overridepublic void setX(int x) {super.setX(x);childX = x;System.out.println("ChildX 被赋值为 " + x);}public void printX() {System.out.println("ChildX = " + childX);}}public class TryInitMain {public static void main(String[] args) {ChildClass cc = new ChildClass();cc.printX();}
}

当然是1啦,子类构造函数执行才会真的初始化里面的值,道理不难,但是要真的理解

另一个小插曲


public class ParseFile4OOM {public static void main(String[] args) {List<Map<String, String>> lst = new ArrayList<>();for (int i = 0; i < 100000; i++) {Map<String, String> map = new HashMap<>(3);map.put("Column1".intern(), "Content1".intern());map.put("Column2".intern(), "Content2".intern());map.put("Column3".intern(), "Content3".intern());lst.add(map);}Map<String, List<Map<String, String>>> contentCache = new HashMap<>();contentCache.put("contents".intern(), lst);}
}

 JDK8引入了 String 常量池。同时,Hashmap 在这个业务场景下,容积是固定的,所以,就不应该给它多分配空间,就固定死为 3。

new 对象的过程

虚拟机遇到一条new指令时,首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过
程。类加载就是把class加载到JVM的运行时数据区的过程。

什么意思?

class Lava { 
private int speed = 5; // 5 kilometers per hour 
void flow() { 
} 
}class Volcano { 
public static void main(String[] args) { 
Lava lava = new Lava(); 
lava.flow(); 
} 
}

为了运行这个程序,你以某种方式把“Volcano”传给了jvm。有了这个名字,jvm找到了这个类文件(Volcano.class)并读入,它从类文件提取了类型信息并放在了方法区中,通过解析存在方法区中的字节码,jvm激活了main()方法,在执行时,jvm保持了一个指向当前类(Volcano)常量池的指针

注意jvm在还没有加载Lava类的时候就已经开始执行了。正像大多数的jvm一样,不会等所有类都加载了以后才开始执行,它只会在需要的时候才加载。

main()的第一条指令告知jvm为列在常量池第一项的类分配足够的内存。jvm使用指向Volcano常量池的指针找到第一项,发现是一个对Lava类的符号引用,然后它就检查方法区看lava是否已经被加载了。

检查加载
首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查类是否已经被加载、解
析和初始化过。

符号引用:以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,JAVA在编译的
时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地
址(实际地址),就用符号引用来代替,而在类的解析阶段就是为了把这个符号引用转化成为真正的
地址的阶段。
假设People类被编译成一个class文件时,如果People类引用了Tool类,但是在编译时People类并
不知道引用类的实际内存地址,因此只能使用符号引用(org.simple.Tool)来代替。而在类装载
器装载People类时,此时可以通过虚拟机获取Tool类的实际内存地址,因此便可以既将符号
org.simple.Tool替换为Tool类的实际内存地址。

分配内存
完成类的加载检查后,虚拟机将为新生对象分配内存。为对象分配空间的任务等同于把一块确定大小的
内存从Java堆中划分出来。

内存从Java堆中划分出来。
指针碰撞
如果Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个
指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等
的距离,这种分配方式称为—指针碰撞。
空闲列表
如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指
针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块
足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为—空闲列表。
选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整
理功能决定。

并发安全
除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即
使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配
内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。

内存空间初始化
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。这一步操作保证了对象的实例字段
在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。(如int值为
0,boolean值为false等等)。
设置
完成空间初始化后,虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的
元数据信息(Java classes在Java hotspot VM内部表示为类元数据)、对象的哈希码、对象的GC分代年
龄等信息。这些信息存放在对象的对象头之中。
对象初始化
在以上工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了。但从Java程序的视角来看,
对象创建才刚刚开始,所有的字段都还为零值。所以,一般来说,执行new指令之后会接着把对象按照
程序员的意愿进行初始化(构造方法),这样一个真正可用的对象才算完全产生

那么问题来了,对象的内存分配在堆上,那么一个全局变量赋值给两个局部变量会出现互相影响的情况吗?

对象在Java中是分配在堆上的,但每个线程在操作对象时,操作的是对象的引用而不是对象本身。因此,即使对象是在堆上分配的,各个线程分别操作对象引用,不会直接影响到堆上的对象。

让我们解释一下:

  1. 对象引用: 在Java中,变量存储的是对象的引用,而不是对象本身。当你创建一个对象时,实际上在堆上分配了内存,并且变量存储的是指向该对象的引用。多个变量可以引用同一个对象。

  2. 线程操作: 当你在不同的线程中将对象引用赋给不同的局部变量时,每个线程操作的是各自的局部变量和引用:虽然 localVar1localVar2 都引用了 globalObject,但它们是独立的局部变量,互不影响。

 总的来说,尽管对象在堆上分配,但在多线程环境中,线程之间的独立性和引用的独立性通常由于每个线程操作自己的局部变量而得以保持,因此不会产生直接的影响。线程1和线程2的操作会影响到 globalObject 引用所指向的对象,最终的结果会体现在 globalObject 对象上。

GC的流程是怎么样的

说到GC垃圾回收,首先要知道什么是“垃圾”,垃圾就是没有用的对象,那么怎样判定一个对象是不是垃
圾(能不能被回收)?Java 虚拟机中使用一种叫作可达性分析的算法来决定对象是否可以被回收。

可达性分析就通过一组名为”GC Root"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径
称为引用链,最后通过判断对象的引用链是否可达来决定对象是否可以被回收。

GC Root指的是:
Java 虚拟机栈(局部变量表)中的引用的对象。也就是正在运行的方法中的局部变量所引用的对象
方法区中静态引用指向的对象。也就是类中的static修饰的变量所引用的对象
方法区中常量引用的对象。
仍处于存活状态中的线程对象。
Native 方法中 JNI 引用的对象。

优点
可达性分析可以解决引用计数器所不能解决的循环引用问题。即便对象a和b相互引用,只要从GC Roots
出发无法到达a或者b,那么可达性分析便不会将它们加入存活对象合集之中。

缺点
在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为null)或
者漏报(将引用设置为未被访问过的对象)。误报并没有什么伤害,Java虚拟机至多损失了部分垃圾回收的
机会。漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。 一旦从原引用访问已经
被回收了的对象,则很有可能会直接导致Java虚拟机崩溃。

垃圾回收算法
在标记出对象是否可被回收后,接下来就需要对可回收对象进行回收。基本的回收算法有:标记-清理、
标记-整理与复制算法。
标记清除算法
从”GC Roots”集合开始,将内存整个遍历一次,保留所有可以被 GC Roots 直接或间接引用到的对象,
而剩下的对象都当作垃圾对待并回收,过程分为 标记 和 清除 两个步骤。

优点:实现简单,不需要将对象进行移动。
缺点:这个算法需要中断进程内其他组件的执行(stop the world),并且可能产生内存碎片,提
高了垃圾回收的频率。


标记整理算法
与标记-清除不同的是它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。最
后,清理边界外所有的空间。
优点:这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
缺点:所谓压缩操作,仍需要进行局部对象移动,所以一定程度上还是降低了效率。

简单说就是把所有数据压缩到内存条一端

复制算法
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中。之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收

那么作为一个开发者,我们知道了GC的流程应该学到什么呢?

避免频繁GC的目标之一就是减少标记阶段和清理阶段的发生次数,以降低对系统性能的影响。

以下是一些使用对象的最佳实践,可以帮助避免频繁的垃圾回收:

  1. 对象池(Object Pooling): 通过使用对象池,可以重复使用已经存在的对象,而不是频繁地创建和销毁对象。这样可以减少新生代的垃圾回收频率,因为不需要频繁分配和释放内存。

  2. 缓存大对象: 对于一些大对象,特别是那些生命周期较长且频繁使用的对象,考虑进行缓存而不是频繁地创建和销毁。这有助于减少老年代的垃圾回收频率。

  3. 限制对象的作用域: 将对象的作用域限制在其真正需要的地方,避免对象在不需要的时候仍然被引用。对象的生命周期越短,它占用的内存就越容易被及时回收。

  4. 谨慎使用Finalizer: 避免过度依赖finalize方法。虽然Java提供了finalize方法供对象进行资源释放,但过度依赖它可能导致不可预测的垃圾回收行为。

  5. 合理设置堆大小: 根据应用程序的需求和性能特征,合理设置堆大小,避免出现频繁的Full GC。

  6. 合理选择垃圾回收器: 根据应用程序的性能需求和特性,选择合适的垃圾回收器。不同的垃圾回收器有不同的特点和适用场景。

  7. 使用软引用和弱引用: 对于一些可以被回收的对象,可以考虑使用软引用或弱引用,以便在内存不足时更容易被回收。

Java中对象如何晋升到老年代? 

实际上有四种情况可能会导致对象晋升老年代:
大对象直接进入老年代
年龄超过阈值
动态对象年龄判定
年轻代空间不足

那么进入老年代的对象一定会被回收嘛?

不一定。虽然老年代中的对象通常是存活时间较长的对象,但并不是所有进入老年代的对象都会被回收。老年代中的对象会随着应用程序的执行而逐渐积累,而垃圾回收器会根据其算法和策略在适当的时候尝试回收不再被引用的对象。

老年代中的垃圾回收通常是由Full GC(Full Garbage Collection)触发的,Full GC 会对整个堆进行回收,包括新生代和老年代。Full GC 的触发条件通常是在老年代没有足够空间分配一个大对象,或者老年代的碎片化导致无法找到足够的连续空间来分配对象。

在进行 Full GC 时,垃圾回收器会标记并清理不再被引用的对象,释放它们占用的内存。被标记为不再被引用的老年代对象将会被回收,但并非所有老年代中的对象都会被回收。

判断对象是否被回收,有哪些GC算法?

是否掌握可达性分析法了解如何确定对象是否可被回收,从而避免程序内存泄露问题

而除了可达性分析法之外,还有被淘汰的引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就
减1;任何时刻计数器为0的对象就是不可能再被使用的。

目前主流的java虚拟机都摒弃掉了这种算法,最主要的原因是它很难解决对象之间相互循环引用的问
题,尽管该算法执行效率很高。

那么什么是 对象之间相互循环引用?

简单的例子两个单例类之间直接互相引用 

如果你发现两个单例类需要相互引用,可能需要重新考虑你的设计。以下是一些可能的解决方案:

  1. 合并成一个单例类: 如果两个单例类之间存在强耦合,可能它们应该被设计成一个单例类,以减少依赖关系。

  2. 通过接口/抽象类解耦: 如果两个单例类确实需要相互引用,可以考虑引入接口或抽象类,将它们的关系抽象出来,降低直接依赖。

  3. 使用依赖注入: 考虑通过依赖注入的方式来解耦,将一个单例类的实例注入到另一个单例类中,而不是直接在类内部进行引用。

  4. 重新评估设计: 如果发现两个单例类之间的关系过于复杂,可能需要重新评估系统的设计,考虑是否有更清晰、更简单的方式来组织代码。

 

Class会不会回收?用不到的Class怎么回收?

Java 虚拟机理论上会回收Class,Class要被回收,条件比较"苛刻",必须同时满足以下的条件:
1、该类的所有实例都已经被回收,即 Java 堆中不存在该类及其任何派生子类的实例
2、加载该类的类加载器已经被回收
3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
Java 虚拟机允许对满足上述三个条件的无用类进行回收,但并不是说必然被回收,仅仅是允许而已

JVM模型

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

近期知识点随笔

菜单查询&#xff08;编写权限时的细节&#xff09; 菜单查询list为了侧边框展示更完整&#xff08;不报空指针&#xff09; 登录时&#xff08;用户名&#xff09;查询出多个结果&#xff08;保证用户名唯一&#xff09; 文件上传 前端 对权限与菜单绑定的修改&#xff08;实…

【数据结构】树的概念以及二叉树

目录 1 树概念及结构 1.1 树的概念 1.3 树的存储 2 二叉树的概念及结构 2.1 概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4 二叉树的存储结构 1 树概念及结构 1.1 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组…

04 # 第一个 TypeScript 程序

初始化项目以及安装依赖 新建 ts_in_action 文件夾 npm init -y安装好 typescript&#xff0c;就可以执行下面命令查看帮助信息 npm i typescript -g tsc -h创建配置文件&#xff0c;执行下面命令就会生成一个 tsconfig.json 文件 tsc --init使用 tsc 编译一个 js 文件 新…

解决:AttributeError: ‘NoneType’ object has no attribute ‘shape’

解决&#xff1a;AttributeError: ‘NoneType’ object has no attribute ‘shape’ 文章目录 解决&#xff1a;AttributeError: NoneType object has no attribute shape背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&…

Vue3集成ThreeJS实现3D效果,threejs+Vite+Vue3+TypeScript 实战课程【一篇文章精通系列】

Vue3集成ThreeJS实现3D效果&#xff0c;threejsViteVue3TypeScript 实战课程【一篇文章精通系列】 项目简介一、项目初始化1、添加一些依赖项 二、创建3D【基础搭建】1、绘制板子&#xff0c;立方体&#xff0c;球体2、材质和光照3、材质和光照和动画4、性能监控5、交互控制6、…

pathlib --- 面向对象的文件系统路径

目录 基础使用 纯路径 通用性质 运算符 访问个别部分 方法和特征属性 具体路径 方法 对应的 os 模块的工具 3.4 新版功能. 源代码 Lib/pathlib.py 该模块提供表示文件系统路径的类&#xff0c;其语义适用于不同的操作系统。路径类被分为提供纯计算操作而没有 I/O 的 …

在Spring Boot中隔离@Async异步任务的线程池

在异步任务执行的时候&#xff0c;我们知道其背后都有一个线程池来执行任务&#xff0c;但是为了控制异步任务的并发不影响到应用的正常运作&#xff0c;我们需要对线程池做好相关的配置&#xff0c;以防资源过度使用。这个时候我们就考虑将线程池进行隔离了。 那么我们为啥要…

FIORI /N/UI2/FLP 始终在IE浏览器中打开 无法在缺省浏览器中打开

在使用/N/UI2/FLP 打开fiori 启动面板的时候&#xff0c;总是会在IE浏览器中打开&#xff0c;无法在缺省浏览器打开 并且URL中包含myssocntl 无法正常打开 启动面板 这种情况可以取消激活ICF节点/sap/public/myssocntl

SpringBoot项目打成jar包后,上传的静态资源(图片等)如何存储和访问

1.问题描述&#xff1a; 使用springboot开发一个项目&#xff0c;开发文件上传的时候&#xff0c;通常会将上传的文件存储到资源目录下的static里面&#xff0c;然后在本地测试上传文件功能没有问题&#xff0c;但是将项目打成jar包放到服务器上运行的时候就会报错&#xff0c…

IDC MarketScape2023年分布式数据库报告:OceanBase位列“领导者”类别,产品能力突出

12 月 1 日&#xff0c;全球领先的IT市场研究和咨询公司 IDC 发布《IDC MarketScape:中国分布式关系型数据库2023年厂商评估》&#xff08;Document number:# CHC50734323&#xff09;。报告认为&#xff0c;头部厂商的优势正在扩大&#xff0c;OceanBase 位列“领导者”类别。…

STM32 定时器TIM

单片机学习 目录 文章目录 前言 一、TIM简介 二、STM32的三种定时器 2.1基本定时器 2.1.1定时中断功能 1. 时钟源 2. 预分频器 3. 计数器 4. 自动重装寄存器 5.更新中断和更新事件 2.1.2主模式触发DAC功能 2.2 计数模式 2.2通用定时器 2.2.1 时钟源 外部时钟模式2 外部时钟模式…

Java中的异常你了解多少?

目录 一.认识异常二.异常分类三.异常的分类1.编译时异常2.运行时异常 四.异常的处理1.LYBL&#xff1a;事前防御型2.EAFP&#xff1a;事后认错型 五.异常的抛出Throw注意事项 六.异常的捕获1.异常的捕获2.异常声明throws3.try-catch捕获并处理 七.自定义异常 一.认识异常 在Jav…

一文带你了解网络安全简史

网络安全简史 1. 上古时代1.1 计算机病毒的理论原型1.2 早期计算机病毒1.3 主要特点 2. 黑客时代2.1 计算机病毒的大流行2.2 知名计算机病毒2.3 主要特点 3. 黑产时代3.1 网络威胁持续升级3.2 代表性事件3.3 主要特点 4 高级威胁时代4.1 高级威胁时代到来4.2 著名的APT组织4.3 …

基于A*的网格地图最短路径问题求解

基于A*的网格地图最短路径问题求解 一、A*算法介绍、原理及步骤二、Dijkstra算法和A*的区别三、A*算法应用场景四、启发函数五、距离六、基于A*的网格地图最短路径问题求解实例分析完整代码 七、A*算法的改进思路 一、A*算法介绍、原理及步骤 A*搜索算法&#xff08;A star al…

Echarts大屏可视化_03 定制柱状图

柱状图模块引入 1.找到合适的图表 在echarts中寻找与目标样式相近的图表 Examples - Apache ECharts 2. 引入柱状图 使用立即执行函数构建&#xff0c;防止变量全局污染 实例化对象 将官网中提供的option复制到代码中&#xff0c;并且构建图表 // 柱状图模块1 (function () {/…

WEB渗透—反序列化(十)

Web渗透—反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩哔_…

【Linux】vim-多模式的文本编辑器

本篇文章内容和干货较多&#xff0c;希望对大家有所帮助&#x1f44d; 目录 一、vim的介绍 1.1 vi 与 vim的概念1.2 Vim 和 Vi 的一些对比 二、vim 模式之间的切换 2.1 进入vim2.2 [正常模式]切换到[插入模式]2.3 [插入模式]切换至[正常模式]2.4 [正常模式]切换至[底行模式…

C/C++---------------LeetCode第876. 链表的中间结点

链表的中间结点 题目及要求双指针在main内使用 题目及要求 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 示例 2&#xff1a; 双指针 思路&#xff1a;分别定义快慢指针…

STM32CubeMx+MATLAB Simulink点灯程序

STM32CubeMxMATLAB点灯程序 ✨要想实现在MATLAB Simulink环境下使用STM32&#xff0c;前提是已经搭建好MATLAB环境并且安装了必要的Simulink插件&#xff0c;以及对应的STM32支持包。 &#x1f33f;需要准备一块所安装支持包支持的STM32开发板. &#x1f516;具体支持包详情页…

LeetCode(45)最长连续序列【哈希表】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 最长连续序列 1.题目 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&a…