JVM的小知识总结

news2024/12/24 0:07:06

加载时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),并且可能产生内存碎片,提
高了垃圾回收的频率。


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

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

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

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

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

相关文章

C语言你爱我么?(ZZULIOJ 1205:你爱我么?)

题目描述 LCY买个n束花准备送给她暗恋的女生&#xff0c;但是他不知道这个女生是否喜欢他。这时候一个算命先生告诉他让他查花瓣数&#xff0c;第一个花瓣表示"爱"&#xff0c;第二个花瓣表示"不爱"&#xff0c;第三个花瓣表示"爱"..... 为了使最…

Unity UGUI的自动布局-LayoutGroup(水平布局)组件

Horizontal Layout Group | Unity UI | 1.0.0 1. 什么是HorizontalLayoutGroup组件&#xff1f; HorizontalLayoutGroup是Unity UGUI中的一种布局组件&#xff0c;用于在水平方向上对子物体进行排列和布局。它可以根据一定的规则自动调整子物体的位置和大小&#xff0c;使它们…

间接法加窗分析信号的功率谱

本篇文章是博主在通信等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对通信等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在 通信领域笔记&#xff…

【图解系列】一张图带你了解 DevOps 生态工具

一张图带你了解 DevOps 生态工具 ✅ 协作&#xff08;Collaborate&#xff09;&#xff1a;JIRA、Confluence 大家肯定不陌生了&#xff0c;我之前也写过利用 Jekyll 搭建个人博客的帖子。✅ 构建&#xff08;Build&#xff09;&#xff1a;常用的 SCM&#xff08;Software Con…

每日一题--删除链表的倒数第 N 个结点

破阵子-晏殊 燕子欲归时节&#xff0c;高楼昨夜西风。 求得人间成小会&#xff0c;试把金尊傍菊丛。歌长粉面红。 斜日更穿帘幕&#xff0c;微凉渐入梧桐。 多少襟情言不尽&#xff0c;写向蛮笺曲调中。此情千万重。 目录 题目描述&#xff1a; 思路分析&#xff1a; 方法及…

01 _ 高并发系统:它的通用设计方法是什么?

我们知道&#xff0c;高并发代表着大流量&#xff0c;高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案&#xff0c;从而抵抗巨大流量的冲击&#xff0c;带给用户更好的使用体验。这些方案好似能操纵流量&#xff0c;让流量更加平稳地被系统中的服务和组件…

全面(16万字)深入探索深度学习:基础原理到经典模型网络的全面解析

前言 Stacking(堆叠) 网页调试 学习率&#xff1a;它决定了模型在每一次迭代中更新参数的幅度激活函数-更加详细 激活函数的意义: 激活函数主要是让模型具有非线性数据拟合的能力&#xff0c;也就是能够对非线性数据进行分割/建模 如果没有激活函数&#xff1a; 第一个隐层: l…

php订单发起退款(余额和微信支付)

index.html <a class="btn btn-danger btn-change btn-tuikuan btn-disabled" href="javascript:;"><i class="fa fa-tuikuan"></i> 订单退款</a>-->order.js // 为表格绑定事件Table.api.bindevent(table);//退款…

Android : Intent(意图)_页面跳转、传递数据_简单应用

示例图&#xff1a; MainActivity.java package com.example.myintent;import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContract; import andro…

【Web】CmsEasy 漏洞复现

访问主页 到处点一点没啥发现 扫目录 访问/admin 账号密码都是admin admin,不知道为什么&#xff0c;这里就先当作是默认吧 &#xff08;其实都是信息检索&#xff0c;能在网上搜到就行hhh&#xff09; 登录成功 看到左边列表有模板&#xff0c;心里大概有数了哈 进行一波历…

不要再往下翻了,你要的女宝穿搭我都有哦

分享女儿的睡衣穿搭 清新自然的浪漫紫 一眼就击中了我的心巴 软糯亲肤上身体验感超赞 轻松自在无束缚 防风又保暖&#xff0c;居家外出都可哦

Python 安装django-cors-headers解决跨域问题

一、PythonCorsHeaders概念 PythonCorsHeaders是一个轻量级的Python工具&#xff0c;用于解决跨域HTTP请求的问题。它允许你指定哪些网站或IP地址可以访问你的站点&#xff0c;并控制这些站点可以访问哪些内容。 现代网站越来越多地使用Ajax技术&#xff0c;使得浏览器能够从不…

RK3588硬编解码MPP环境配置

1. 简介 瑞芯微提供的媒体处理软件平台&#xff08;Media Process Platform&#xff0c;简称 MPP&#xff09;是适用于瑞芯微芯片系列的 通用媒体处理软件平台。该平台对应用软件屏蔽了芯片相关的复杂底层处理&#xff0c;其目的是为了屏蔽不 同芯片的差异&#xff0c;为使用者…

零基础学python第一天||数和四则运算

数和四则运算 一提到计算机&#xff0c;当然现在更多人把她叫做电脑&#xff0c;这两个词都是指computer。不管什么&#xff0c;只要提到她&#xff0c;普遍都会想到她能够比较快地做加减乘除&#xff0c;甚至乘方开方等。乃至于&#xff0c;有的人在口语中区分不开计算机和计…

科研/比赛必备工具及系列笔记集合

科研/比赛必备工具及系列笔记集合 零、前言一、常用工具系列1.1 笔记平台使用感受系列1.2 常用开发平台系列 二、论文系列2.1 检索工具系列2.2 投稿调研系列2.3 常见国际期刊/会议2.4 常见中文核心期刊/会议 三、文献系列3.1 画图工具系列3.2 翻译工具系列3.3 英文纠正系列3.4 …

Vue + Element UI 实现复制当前行数据功能及解决复制到新增页面组件值不更新的问题

文章目录 引言第一部分&#xff1a;复制当前行数据功能的实现1.1 环境准备1.2 创建表格并渲染数据1.3 解决复制的数据不更新问题 第二部分&#xff1a;拓展知识2.1 Vue的响应性原理2.2 Element UI的更多用法 结语 Vue Element UI 实现复制当前行数据功能及解决复制到新增页面组…

JAVA小游戏“简易版王者荣耀”

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt;import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; im…

初出茅庐的小李之C语言必备知识预处理

编译预处理 编译预处理就是在编译源代码之前进行的一系列处理&#xff0c;将源程序中的一些特殊命令进行展开或处理&#xff0c;生成扩展的源代码。这些特殊命令通常以“#”开头&#xff0c;占单独的行&#xff0c;语句尾部不需要加分号。 宏定义 (#define)是一种常见的编译…

Leetcode—55.跳跃游戏【中等】

2023每日刷题&#xff08;四十&#xff09; Leetcode—55.跳跃游戏 贪心法实现代码 #define MAX(a, b) ((a > b)? (a): (b))bool canJump(int* nums, int numsSize) {int k 0;for(int i 0; i < numsSize; i) {if(i > k) {return false;}k MAX(k, i nums[i]);}r…

[架构之路-251]:目标系统 - 设计方法 - 软件工程 - 软件建模 - 什么是建模,什么是软件系统建模?软件系统阶段性建模?正向建模与反向建模?

目录 前言&#xff1a; 一、什么是建模 1.1 什么是建模 1.2 常见的建模的方式与种类 二、什么是软件系统建模 2.1 软件系统建模的概念 2.2 软件系统常见的三种建模方法和手段 2.3 软件系统建模的常见工具 三、软件系统阶段性建模 3.1 软件工程在不同阶段对软件系统进…