java sincerity_JVM 面试必备(下)

类加载的5个过程

类加载的本质

将描述类的数据 从Class文件加载到内存并且对数据进行校验 转换解析和初始化 最终新城虚拟机直接使用java使用类型

类加载过程

加载

作用

将外部的Class文件加载到虚拟机并且存储到方法区内

具体流程

通过类名的全限定名来获取定义此类的二进制数据

将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构

在内存中生成一个代表这个类的java.lang.class对象 作为方法区该类的各种数据的访问入口

注意

数组类是通过java虚拟机直接创建 不通过类的加载机制

验证

作用 确

保加载进来的class文件包含的信息符合jvm的要求

具体流程

文件格式的校验

元数据校验

字节码校验

符号引用校验

准备

作用

为类变量分配内存 并且设置类变量的初始值

具体流程

为类的static变量在方法区中分配内存

将上述变量的初始值设置为0

注意

实例变量不在该阶段分配内存

若该类为常量(final修饰) 直接复制开发者定义的值

解析

作用

将常量池内的符号引用转为直接引用

具体流程

解析对象(类/接口) 方法 (类方法 接口方法 方法类型 方法句柄) 字段

注意

实例变量不在该阶段分配内存

因为类方法和私有方法符合 "编译器可知 , 运行期不可变" 的要求 即不会被继承或者重写 所以适合类加载过程进行解析

若类变量为常量 (final 修饰 ) 则直接赋值开发者定义的值

初始化

作用

初始化类变量 静态语句块

具体流程

生成类构造器 clinit() 即合并所有类变量和静态语句块

执行clinit()方法

注意

类构造器clinit区别于类构造器 init

不需要调用父类构造器

子类clinit执行前 父类的clinit一定会被执行

虚拟机第一个执行的clinit是 java.lang.object

静态语句块只可被赋值不能被访问

接口与类不同 执行子接口的clinit并不需要执行负借口的clinit

对象的创建 内存分配 访问定位

对象的创建

A a =new A(); //当遇到关键字new指令时,Java对象创建过程便开始

8b8094b18e46

类加载过程.png

加载过程

类加载检查

检查该new指令的参数 是否在常量池中定位到了一个类的符号引用 没有即创建对象失败

检查该类符号引用代表的类是否已经被加载,解析和初始化过

如果没有 需要先执行类的加载过程

为对象分配内存

对象所需要的内存大小在类加载完成后便可以完全确定

内存分配 根据java堆内存是否绝对规整分为

指针碰撞 Compat 收集器

假设java堆内存绝对规整 内存分配采用指针碰撞

分配形式: 已使用内存在一边 未使用的在另一边 中间放一个座位分界点的指示器

那么 分配对象内存 = 指针针向未使用内存一定一段与对象大小相等的距离

空闲列表 CMS 收集器

假设java堆内存不规整 内存分配将采用空闲列表

分配形式 :虚拟机维护着一个记录可用内存块的列表 在分配时从列表中找到一块足够大的空间划分给对象实例 ,并更新列表上的记录

内存创建在虚拟机中非常常见 存在并发情况下也会引起线程不安全

解决办法

同步处理分配内存空间 虚拟机采用CAS + 失败重试 保证更新操作的原子性

把内存分配行为按照线程划分在不同的内存空间进行

即每个线程在 Java堆中预先分配一小块内存(本地线程分配缓冲(Thread Local Allocation Buffer ,TLAB)),哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时才需要同步锁。

虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。

将内存空间初始化为0

内存分配完成后 虚拟机需要将分配的内存空间初始化为0

保证对象的实例字段在使用时不可复制就能直接适应 (默认为0)

如果使用本线程分配缓冲(TLAB) 这一工作过程可以提前至TLAB分配时进行

对对象进行必要的设置

设置对象是哪个类的实例 如何找到类的源数据 对象的哈希吗 对象的GC分代信息等

这个信息存放在对象的对象头中

对象的内存分配

在java对象创建后 打底是如何被存储在java内存中的呢

在java虚拟机中 对象内存中 存储布局可以分为三块

1. 对象头

1.1 对象自身的运行时数据 ( Mark Word )

如Hash 码 GC分代 锁状态 线程持有的锁 偏向线程 id 偏向时间戳

该部分数据设计成1个非固定的数据结构 一边在绩效的空间存储更多信息

1.2 对象类型指针

对象指向它的类元数据的指针

虚拟机通过这个指针确定这个对象是哪个类的实例

1.3 数组的对象头

如果是对象是数组 name在对象头中还有一块记录数组长度的数据

因为虚拟机可以通过普通java 对象的元数据确定对象的大小 但是从数组的元数据中无法确定数组的大小

2. 实例数据

存储的信息 对象真正有效的信息

代码中定义的字段内容

这部分数据的存储顺序会受到虚拟机分配参数(FieldAllocationStyle)和字段在Java源码中定义顺序的影响。

3. 对齐填充 (非必须)

存储 占位符

占位作用

因为对象的大小必须是8字节的整数倍(即对象的大小不是8字节的整数倍),就需要通过对齐填充来补全。

对象的访问定位

对象建立后 如何去访问对象?

实际上 需要访问的是 对象类型数据 和 对象实例数据

java 程序 通过 栈上的引用类型(reference) 来访问java栈上的对象

句柄访问

直接指针访问

JVM 分派

分派: 确定执行那个方法的过程

静态分派

1.1 定义

根据变量的静态类型进行方法分派的行为 根据变量的静态类型 确定执行那个方法 发生在编译器 不由JVM来执行

public class Test {

// 类定义

static abstract class Human {

}

// 继承自抽象类Human

static class Man extends Human {

}

static class Woman extends Human {

}

// 可供重载的方法

public void sayHello(Human guy) {

System.out.println("hello,Human!");

}

public void sayHello(Man guy) {

System.out.println("hello Man!");

}

public void sayHello(Woman guy) {

System.out.println("hello Woman!");

}

// 测试代码

public static void main(String[] args) {

Human man = new Man();

Human woman = new Woman();

Test test = new Test();

test.sayHello(man);

test.sayHello(woman);

}

}

// 运行结果

hello,Human!

hello,Human!

1.2 方法重载

重载=静态分派 =根据变量的静态类型确定执行那个重载方法

1.3 变量的静态类型发生变化

强制装换类型 改变变量的静态类型

Human man = new Man();

test.sayHello((Man)man);

1.4 静态分配的优先级匹配

静态分派优先选择参数类型一致的重载方法

没有最合适的方式 进行重载时 会选择(第二优先级)的方法重载 基本数据类型优先级分配

第二优先级顺序 : char > int > long > float > double > character > serializable> object .. args..

最后的args 为可变长参数 可以理解为数组

因为char转为byte 或者short过程不安全 所以不会选择参数类型为byte或者short进行重载

引用类型分配 根据继承关系进行优先级匹配

动态分派

2.1 定义

根据变量的动态类型确定执行那个方法

// 定义类

class Human {

public void sayHello(){

System.out.println("Human say hello");

}

}

// 继承自 抽象类Human 并 重写sayHello()

class Man extends Human {

@Override

protected void sayHello() {

System.out.println("man say hello");

}

}

class Woman extends Human {

@Override

protected void sayHello() {

System.out.println("woman say hello");

}

}

// 测试代码

public static void main(String[] args) {

// 情况1

Human man = new man();

man.sayHello();

// 情况2

man = new Woman();

man.sayHello();

}

}

// 运行结果

man say hello

woman say hello

// 原因解析

// 1. 方法重写(Override) = 动态分派 = 根据 变量的动态类型 确定执行(重写)哪个方法

// 2. 对于情况1:根据变量(Man)的动态类型(man)确定调用man中的重写方法sayHello()

// 3. 对于情况2:根据变量(Man)的动态类型(woman)确定调用woman中的重写方法sayHello()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。
使用 JavaScript 编写的 Squareshooter 游戏及其源代码   项目:使用 JavaScript 编写的 Squareshooter 游戏(附源代码) 这款游戏是双人游戏。这是一款使用 JavaScript 编写的射击游戏,带有门户和强化道具。在这里,每个玩家都必须控制方形盒子(作为射手)。这款射击游戏的主要目标是射击对手玩家以求生存。当它射击对手时,它会获得一分。 游戏制作 该游戏仅使用 HTML 和 JavaScript 开发。该游戏的 PC 控制也很简单。 对于玩家 1: T:朝你上次动作的方向射击 A:向左移动 D:向右移动 W:向上移动 S:向下移动 对于玩家2: L:朝你上次移动的方向射击 左箭头:向左移动 右箭头:向右移动 向上箭头:向上移动 向下箭头:向下移动 游戏会一直进行,直到您成功射击对手或对手射击您为止。游戏得分显示在顶部。所有游戏功能均由 JavaScript 设置,而布局和其他次要功能则由 HTML 设置。 如何运行该项目? 要运行此项目,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox。要运行此游戏,首先,通过单击 index.html 文件在浏览器中打开项目。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值