让开发
成为一种享受!

2020 Java相关 面试题整理

JVM运行时数据区

线程共享部分:方法区、堆内存

线程独占部分:虚拟机栈、本地方法栈、程序计数器

方法区

用来存储加载的类信息、常量、静态变量、编译后的代码等数据

堆内存

用来存放对象的区域,可以细分为:老年代、新生代(Eden、From Survivor、To Survivor)

虚拟机栈

每个线程都在这个空间有一个私有的空间,线程栈由多个栈帧组成。一个线程会执行一个或者多个方法,一个方法对应一个栈帧。

栈帧内容包括:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等,栈内存默认最大是1M。

本地方法栈

和虚拟机栈功能类似,虚拟机栈是为虚拟机执行JAVA方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的

程序计数器

记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行Native方法,则计数器值为空。

CPU同一时间,只会执行一条线程中的指令。JVM多线程会轮流切换并分配CPU执行时间,为了线程切换后,需要通过程序计数器来恢复正确的执行位置。

Java类加载过程

过程包括加载、验证、准备、解析、初始化

加载是类加载的第一个过程,在这个阶段,将完成一下三件事情:

  • 通过类的全限定名获取类的二进制流
  • 将该二进制流中的静态存储结构转化为方法区运行时数据结构
  • 内存中生成该类的 java.lang.Class 对象,作为该类的数据访问入口。

验证的目的是为了确保 Class 文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:

  • 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型。
  • 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
  • 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
  • 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。

解析阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

初始化是类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。

描述一下JVM加载class文件的原理机制?

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。

类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。

最后JVM对类进行初始化,包括:

1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:

启动类加载器(Bootstrap ClassLoader)、

扩展类加载器(Extension ClassLoader)、

应用程序类加载器(Application ClassLoader)、

用户自定义类加载器(java.lang.ClassLoader的子类)。

类加载过程采取了双亲委派模型机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:

Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;

Application:应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

破坏双亲委派模型:线程上下文类加载器、OSGi类加载器

线程上下文类加载器:Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

OSGi类加载器:作代码热替换(HotSwap)、模块热部署(Hot Deployment)等用途。

Java内存模型

Java内存模型的主要目标是定义程序中变量的访问规则。即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节。

1)Java内存模型将内存分为了主内存工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去

2)定义了几个原子操作,用于操作主内存和工作内存中的变量

3)定义了volatile变量的使用规则

4)happens-before,即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的

延伸阅读

Java内存模型中涉及到的概念有:

主内存:java虚拟机规定所有的变量(不是程序中的变量)都必须在主内存中产生,为了方便理解,可以认为是堆区。可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。

工作内存:java虚拟机中每个线程都有自己的工作内存,该内存是线程私有的为了方便理解,可以认为是虚拟机栈。可以与前面说的高速缓存相比。线程的工作内存保存了线程需要的变量在主内存中的副本。虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量。不同的线程之间也不能相互访问对方的工作内存。如果线程之间需要传递变量的值,必须通过主内存来作为中介进行传递。

内存间交互操作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。

unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用

load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。

write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

不允许read和load、store和write操作之一单独出现

不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。

不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。

一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。

一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现

如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值

如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。

对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

Jvm垃圾收集算法

扫码关注公众号:Java开发乐园

在聊天窗口回复:vip

输入验证码,即可永久解锁本站全部文章

扫码关注【Java开发乐园】

Java开发乐园

扫码关注【东方】

微信:东方

验证码:
赞(1) 打赏
转载请注明出处:Java开发乐园 » 2020 Java相关 面试题整理

来评论一下嘛~ 抢沙发

评论前必须登录!

 

我愿终生等候,换你刹那凝眸

留点🐾印

打赏即是一种肯定,谢谢您的肯定

支付宝扫一扫打赏

微信扫一扫打赏