Java面经-jvm虚拟机

JVM复习重点(待续)

1. JVM的主要组成部分和作用

JDK1.8之前:

1)运行时数据区域:

程序计数器和虚拟机栈和本地方法栈都是线程私有的。

  • 程序计数器:
    • 线程执行java代码时,存放虚拟机字节码的指令地址,执行Native代码时,值为空。可以看着做字节码的行号指示器。
  • Java虚拟机栈:
    • 每个虚拟机栈执行方法执行时也会创建线程私有的栈帧。
    • 存放java方法执行的内存模型。栈帧:局部变量表,操作数栈,动态链接,方法出口。
      • 局部变量表存放了编译器可知的八种数据类型和对象引用。
    • 局部变量表存放基本数据类型、对象引用、returnAddress类型。
  • 本地方法栈:
    • 为Native方法提供服务。

Java的堆、方法区都是线程共享的。

  • Java堆:

    • 存放对象实例和数组。
    • 垃圾回收的主要区域,称为GC堆,采用分代收集算法,分为新生代和老年代
  • 方法区:

    • 存放加载的类信息常量静态变量(以static修饰的变量和方法)、即时编译器编译的代码。
    • 运行时常量池是方法区的一部分。存放编译器生成的各种字面量符号引用

    (这个和堆中的字符串常量池有什么关系?应该是上述常量的一部分)

  • 虚拟机规范可以选择不实现垃圾收集。

2)本地库接口:与本地库接口交互

3)类加载器:根据类的全限定名,将类装载到方法区

4)执行引擎:包括JIT编译器和GC垃圾回收器。

JDK 1.8及之后, 方法区被元空间(Metaspace)取代,是直接内存的一部分。

一个java指令的运行过程:

Java代码(.java)-编译器javac - 字节码文件(.class)- 类加载器 - 内存方法区并封存一个类对象 - 执行引擎翻译 - 底层指令

堆栈的区别:

  • 物理地址:堆不连续、栈连续
  • 内存:堆的内存分配到运行期确定,大小不固定;栈是连续的,分配的大小在编译器确定
  • 内容:栈存放局部变量、操作数栈、返回值,堆存放对象的实例和数组。
  • 注意,静态变量和静态方法位于方法区,但是其所在类的对象依然位于堆。
2. (HotSpot虚拟机中)对象的创建:
  • 类加载:指令中的参数是否能在堆中的常量池中定位到一个类的符号引用。没有,执行类加载。
  • 堆中分配空间:对象所需的内存在类加载这一步就可以确定,通过指针碰撞法或者空闲列表法在堆中分配空间。
  • 保证安全:使用两种方式保证划分空间的动作是安全的。
  • 初始化:将分配到的空间初始化为零值。
  • 写入对象头:将对象是那个类的实例,如何找到类的元数据信息、对象哈希码、对象的GC分代信息写入对象头
  • 执行init方法。

注意:init是实例构造器,调用实例的构造方法;clinit是类构造器。

对象在内存中存储的布局可以分为三部分:对象头、实例数据和对齐填充。其中对象头又包括两部分,一部分是运行时信息:hash码、GC分代信息、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳;另一部分是类型指针,指向类的元数据(位于方法区)。

3.为对象分配内存

指针碰撞法:Java堆是规整的,用过的在一边,空闲的在另一边,分配内存时将指针指示器向空闲一端移动和对象大小相等的距离。

空闲列表法:如果Java堆不是规整的,需要由虚拟机维护一个列表来记录哪些内存可用,需要的时候,从列表中查询合适的内存分配给对象,更新列表记录。。

Java堆是否规整由Java使用的垃圾回收器是否带有压缩功能决定,如:标记-整理法的Java堆就是规整的。

4.处理并发安全问题

对象的创建在并发条件下会导致线程的不安全(原因看单例模式)。两种解决方法:

  • 对分配内存空间的动作进行同步处理(CAS+失败重试来保证操作的原子性)

  • 内存分配按照线程划分在不同空间中进行,每个线程在Java堆中预先分配内存,叫“本地线程分配缓冲”

5.对象的访问定位

java程序使用栈上的reference数据来操作堆上的具体对象。

  • 句柄方式:栈的ref指向堆中的句柄池,句柄池有指向堆中对象实例和指向方法区的类型数据的指针。优点:对象实例被移动时,句柄不用修改。
  • 直接指针方式,ref直接指向堆中的对象实例,对象实例的对象头中保存指向方法区类型数据的指针。优点:快。
6.对象创建的不同方式

使用new关键字 - 调用构造函数

使用Class类的newInstance() - 调用构造函数

使用Constructor类的newInstance() - 调用构造函数

使用clone() - 没有调用构造函数

使用反序列化 - 没有调用构造函数

7.简述垃圾回收机制

Java中不需要显式释放对象内存,由虚拟机自动执行。GC线程是专门用于回收垃圾的线程,低优先级,在虚拟机空闲或者堆内存不足时,扫描不需要的对象,添加到待回收的集合中,执行回收。

垃圾回收机制,有效得防止了内存泄露,可以有效地使用可以使用得内存。

8.确认哪些对象可以回收的算法
  • 引用计数法。有一个地方引用,引用计数+1;引用失效,引用计数-1;(很难解决对象之间的循环引用问题)
  • 可达性算法。设定GCroot,从节点往下搜索,搜索走过的路径称为引用链。当一个对象到GCroot没有引用链,称这个对象不可达。
    • GC Roots对象有:
      • 虚拟机栈中(本地变量表)中引用的对象。
      • 方法区中类静态变量引用的对象。
      • 方法区中常量引用的对象。
      • Native方法引用的对象。

引用可以分为:强引用、软引用、弱引用、虚引用

9.永久代(方法区)的垃圾回收

垃圾回收主要发生在堆区,其实方法区也有垃圾回收。永久代的回收主要包括废弃常量和无用的类。永久代满了或者超过临界值,会触发Full GC完全垃圾回收。(JDK8 中移除了永久代,新加了叫元数据区的native内存区)

10.垃圾回收算法:
  • 标记-清除算法:先标记,后清除。缺点:会产生大量的内部碎片。
  • 复制算法:将内存按照容量分为两块,每次将存活的对象复制到另一块上。(新生代)
  • 标记-整理算法:先标记,再清除,后整理,让存活的对象往一侧移动,清理掉端边界以外的内存。
  • 分代收集算法:新生代使用复制算法(死亡率高),老年代使用标记算法。

HotSpot的垃圾回收机制的实现:

  • 使用OopMap数据结构记录所有GC Roots。
  • 只有在安全点才生成OopMap,进入GC。
11.分代收集算法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GfKAkm36-1623304051849)(Java虚拟机第二版.assets/虚拟机分代算法.png)]

新生代-老年代-永久代 。新生代:老年代 = 1:2;新生代中的Eden: To Survivor: From Survivor = 8:1:1.

对象的分配主要在堆上,主要在Eden区,如果启动了本地线程分配缓冲,则优先分配在TLAB上。

对象优先在新生代的Eden区分配,当Eden区没有足够空间分配时,就会发起一次Minor GC;如果Minor GC后还是没有足够的空间,启动分配担保机制在老年代中分配内存。

如果对象在Eden区出生,能被Survivor容纳,将进入Survivor空间,对象年龄+1.

大对象直接在老年代分配。因为新生代是复制算法,这样可以避免大对象在新生代反复复制。

长期存活的对象将进入老年代。

12.分代垃圾回收算法
  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区;

  • 清空 Eden 和 From Survivor 分区;

  • From Survivor 和 To Survivor 分区交换。

  • 每次gc在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。

关于Minor GC 和 Full GC

  • 发生在新生代的GC,频繁,速度快
  • 发生在老年代的GC,至少伴随一次Minor GC,速度很慢。一般使用标记-整理算法。
13.class文件中包含的内容
  • 常量池

  • 访问标志

  • 类索引、父类索引、接口索引集合

  • 字段表集合

常量池可以理解为class文件中的资源仓库。

常量池中存放了两大类常量:字面量和符号引用。

其中,字面量包括:

  • 文本字符串
  • 声明为final的常量值。

符号引用包括:

  • 类和接口的全限定名。
  • 字段的名称和描述符。
  • 方法的名称和描述符。

字段表集合:包括了类级变量和实例级变量

字段可以包含的信息:作用域、类还是实例变量(static)、可变性(final)、并发可见性(volatile)、可被序列化(transient)、字段数据类型。

14. 必须进行类初始化的几种情况(初始化一定会导致类加载)

Java语言,动态可拓展,先编译成class字节码文件,在运行时再执行动态加载(加载到jvm)和动态链接

java的一个class文件对应一个类或者一个接口,类的生命周期一般包括加载、验证、准备、解析、初始化、使用、卸载。

JVM规范规定了初始化的几种情况,加载、验证、准备一定要在此之前。

​ ①new一个对象;读取或者设置一个类的静态字段(final修饰的常量在编译期放入常量池,除外),调用类的静态方法

​ ②使用reflect包反射调用

​ ③调用一个类时发现其父类没有初始化

​ ④main所在的类

​ ⑤Methodhandle

其余的引用方式都不会发生初始化,称为被动引用。而上述导致初始化的方式称为主动引用。

15. java类加载

Java的类加载机制,指的是JVM把类的数据从Class文件加载到内存方法区,并且对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接只用的java类型。

加载:

① 通过类的全限定名获得二进制字节流(zip包、class文件、网络、动态代理技术)(这一步是由类加载器决定的)

②将类的静态存储结构转为方法区的运行时数据结构。

③生成代表这个类的Class对象,作为方法区这个类的各个数据的返回入口。

验证:

文件格式、元数据、字节码、符号引用验证

准备:

给类变量(static修饰的静态变量)在方法区分配内存并且初始化为0。

解析:

将常量池中的符号引用替换为直接引用的过程。

初始化:

真正执行字节码,执行类构造器()方法,收集类变量的赋值动作和静态语句块合并而成。(和实例构造器不同,不需要显式调用父类构造器)

16.四种类加载器
  • 启动类加载器:用于加载Java核心类库
  • 扩展类加载器:用于加载Java扩展库
  • 应用程序类加载器:根据全类名加载Java类
  • 自定义类加载器:继承自java.lang.ClassLoader
17.双亲委派模型

上述四种加载器是父类和子类继承的关系。每一个类+其类加载器,一起确定了在JVM的唯一性。

双亲委派模型:一个类加载器获得类加载请求,不会自己加载这个类,而是把这个请求委派给父类。当传送到最顶层,只有当最顶层的加载器无法完成加载时,子加载器才会加载这个类。

用父类构造器)

16.四种类加载器
  • 启动类加载器:用于加载Java核心类库
  • 扩展类加载器:用于加载Java扩展库
  • 应用程序类加载器:根据全类名加载Java类
  • 自定义类加载器:继承自java.lang.ClassLoader
17.双亲委派模型

上述四种加载器是父类和子类继承的关系。每一个类+其类加载器,一起确定了在JVM的唯一性。

双亲委派模型:一个类加载器获得类加载请求,不会自己加载这个类,而是把这个请求委派给父类。当传送到最顶层,只有当最顶层的加载器无法完成加载时,子加载器才会加载这个类。

好处:保证Java运作的稳定性。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值