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() {}
    @Override
    public 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.mfbz.cn/a/191355.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

利用STM32和MFRC522 IC实现智能卡的读取和数据存储

利用STM32微控制器和MFRC522 RFID读写器芯片&#xff0c;可以实现智能卡的读取和数据存储功能。智能卡是一种集成了RFID技术和存储芯片的卡片&#xff0c;它可以用于身份验证、门禁控制、支付系统等应用场景。下面将介绍如何使用STM32和MFRC522芯片进行智能卡的读取和数据存储&…

【计算机视觉】【图像处理综合应用】路沿检测

实验内容&#xff1a;针对给定的视频&#xff0c;利用图像处理基本方法实现道路路沿的检测&#xff1b; 提示&#xff1a;可利用Hough变换进行线检测&#xff0c;融合路沿的结构信息实现路沿边界定位&#xff08;图中红色的点位置&#xff09;。 处理视频文件 处理视频文件的主…

Nginx(资源压缩)

建立在动静分离的基础之上&#xff0c;如果一个静态资源的Size越小&#xff0c;那么自然传输速度会更快&#xff0c;同时也会更节省带宽&#xff0c;因此我们在部署项目时&#xff0c;也可以通过Nginx对于静态资源实现压缩传输&#xff0c;一方面可以节省带宽资源&#xff0c;第…

GPT4测试 — 答题能力及文档处理能力

创建gdp.txt文件&#xff08;使用word 2013创建的文档测试了也可以&#xff0c;WPS建的不行&#xff09; 上传文件&#xff0c;输入prompt: 请帮我答题&#xff0c;把那个正确答案的选项的字母序号填在&#xff08;&#xff09;中&#xff0c;并返回文件blabla… 给我一个文件…

第二十三章 解析PR曲线、ROC曲线、AUC、AP(工具)

混淆矩阵Confusion Matrix 混淆矩阵定义 混淆矩阵是机器学习中总结分类模型预测结果的情形分析表&#xff0c;以矩阵形式将数据集中的记录按照真实的类别与分类模型预测的类别判断两个标准进行汇总。其中矩阵的行表示真实值&#xff0c;矩阵的列表示预测值&#xff0c;下面我…

可燃气体监测仪助力燃气管网安全监测,效果一览

城市地下管线是指城市范围内供应水、排放水、燃气等各类管线及其附属设施&#xff0c;它们是保障城市正常运转的重要基础设施且影响着城市生命线。其中燃气引发的事故近些年不断增加&#xff0c;由于燃气管线深埋地下环境复杂&#xff0c;所以仅仅依赖人工巡查难以全面有效地防…

PCIE链路训练-状态机描述3

Configuration.Idle 1.当使用8b/10b编码时&#xff0c;non-flit模式下&#xff0c;在所用配置的lane上发送s Idle data Symbols&#xff0c;在flit mode下发送IDLE flit。 2.linkup 0 link两端的component均支持64.0GT/s的速率&#xff0c;根据进入此状态之前发送的8个TS2或…

2023/11/26总结

一些学习记录&#xff1a; 在对数据库进行一系列操作的时候&#xff0c;遇到一个问题&#xff0c;在插入数据的时候&#xff0c;我数据库对应的是自增id&#xff0c;但是插入后想获取到这个id去使用。我以为是不可以马上获取的&#xff0c;然后看到 项目进度 购物车&#xff…

springboot核心原理之@SpringbootApplication

1.SpringbootApplication Configuration标志的类 在spring ioc启动的时候就会加载创建这个类对象 EnableAutoConfiguration 中有两个注解 &#xff08;1&#xff09;AutoConfigurationPackage 扫描主程序包(主程序main所在包及其子包) 可以看到这个类 &#xff1a; static c…

Ubuntu:安装VSCode

参考博客Ubuntu下安装VSCODE_ubuntu安装vscode-CSDN博客中的第二种方式【安装包方式安装】&#xff0c;即可&#xff0c;安装非常easy~~~ 安装包方式安装&#xff1a; 1. 从VSCode官网下载最新版的deb安装包&#xff1a; https://code.visualstudio.com/Download&#xff0c;…

【论文解读】Real-ESRGAN:使用纯合成数据训练真实世界的超分辨率图像

图一是4种超分方法的对比效果 。 0 摘要 尽管在盲超分辨率方面已经进行了许多尝试&#xff0c;以恢复具有未知和复杂退化的低分辨率图像&#xff0c;但它们仍然远远不能解决一般的真实世界退化图像。在这项工作中&#xff0c;我们将强大的 ESRGAN 扩展到一个实际的恢复应用程序…

85基于Matlab的交通设施识别

基于Matlab的交通设施识别。 GUI设计图像处理, 基于数字图像处理&#xff0c;设计实现一个自然场景下公路交通限速标志分割和识别的程序。要求系统具有界面&#xff0c;并实现以下功能&#xff1a; 1&#xff09;读入自然场景下包含交通标志的图像&#xff1b; 2&#xff09;对…

Java多线程并发中部分不并发的问题

写Java实验发现个有意思的问题 三个线程&#xff0c;一个线程打印字符a&#xff0c;一个线程打印字符b&#xff0c;另一个线程打印数字&#xff0c;多次运行结果都是先打印混合输出的ab&#xff0c;完了再打印数字 有图有真相&#xff0c;我运行了10次 完整的代码是这个 clas…

win10下载Remix IDE桌面版以及空白页面的解决

文章目录 Remix IDE 的下载Remix IDE 空白页面的解决 Remix IDE 的下载 到 github 地址 https://github.com/ethereum/remix-desktop/releases 选择exe文件或根据自己电脑版本选择对应的zip文件进行下载&#xff0c;然后正常安装即可。 Remix IDE 空白页面的解决 有时打开Remix…

7 通用数字量输入输出GPIO

文章目录 7.0 GPIO概念7.1 GPIO工作原理7.2 GPIO寄存器以及编程7.2.5 GPIO寄存器编程设置与应用 7.3 GPIO跑马灯7.3.1 LED 输出初始化7.3.2 跑马灯输出实验7.3.3 按键输入实验 7.0 GPIO概念 GPIO&#xff08;general purpose intput output&#xff09;是通用输入输出端口的简…

记一次Kotlin Visibility Modifiers引发的问题

概述 测试环境爆出ERROR告警日志java.lang.IllegalStateException: Didnt find report for specified language&#xff0c;登录测试环境ELK查到如下具体的报错堆栈日志&#xff1a; java.lang.IllegalStateException: Didnt find report for specified language at com.aba.…

7000字详解 动态代理(JDK动态代理 CGLIB动态代理)与静态代理

代理模式 1. 代理模式 概念2. 静态代理3. 动态代理3.1.JDK动态代理3.2.CGLIB动态代理3.3. JDK动态代理和CGLIB动态代理区别 4.静态代理和动态代理区别5.篇末 1. 代理模式 概念 代理模式是一种设计模式。 使用代理对象来替代真实对象&#xff0c;用代理对象去访问目标对象。这样…

笔记(三)maxflow push relabel与图像分割

笔记&#xff08;三&#xff09;maxflow push relabel与图像分割 1. Push-Relabel算法思想2.Push-Relabel算法原理示意图3.Push-Relabel算法具体实例4. push relabel与图割 1. Push-Relabel算法思想 对于一个网络流图: 该算法直观可以这样理解&#xff0c;先在源节点处加入充足…

WordPress无需插件禁用WP生成1536×1536和2048×2048尺寸图片

我们在使用WordPress上传图片媒体文件的时候&#xff0c;是不是看到媒体库中有15361536和20482048的图片文件&#xff0c;当然这么大的文件会占用我们的服务器空间&#xff0c;如何禁止掉呢&#xff1f; function remove_default_image_sizes( $sizes) {unset( $sizes[1536x15…

人力资源管理后台 === 组织架构

目录 1.组织架构-树组件应用 2.组织架构-树组件自定义结构 3.组织架构-获取组织架构数据 4.组织架构-递归转化树形结构 5.组织架构-添加子部门-新建弹层组件 6.组织架构-添加子部门-表单结构 7.组织架构-添加子部门-表单基本校验 8.组织架构-添加子部门-表单业务校验 9…
最新文章