java虚拟机常问面试题(一)

以下内容为笔者观看尚硅谷宋红康老师的视频总结,汇总了笔者曾在面试中被常问的JVM内容,编写的目的一为自身复盘,二为面试准备。本篇为笔者总结的第一篇,第二篇链接如下java虚拟机常问面试题(二)

1、JVM的生命周期

在这里插入图片描述

2、类加载过程

在这里插入图片描述
加载: 通过IO,读取字节码文件;(通过全限定类名获取该类的二进制字节流)
链接
1)校验: 字节码文件是否符合虚拟机要求
2)准备:为类变量赋初始值
3)解析:将常量池内的符号引用转换为直接引用的过程。
初始化: 就是执行类构造器方法 < clinit > ()的过程,对类的静态变量初始化为指定的值以及执行静态代码块;

3、双亲委派机制(面试常问)

1)定义: Java虚拟机对class文件采取的是按需加载方式,也就是需要某类时才会将其加载到内存中生成class对象,而加载某类的class文件时,java虚拟机采用的是双亲委派模式,即把请求交由父类处理,是一种任务委派模式。
2)工作原理图解如下:
在这里插入图片描述

  • 如果一个类加载器收到了类加载请求,它并不会第一时间就去加载,而是一级级向上委派;
  • 如果父类加载器还有其父类加载器,则进一步向上委派,请求最终将达到顶层的启动类加载器;
  • 如果父类加载器可以完成类加载任务,则成功返回,否则再交由子加载器本身去加载,这就是双亲委派模式;
    3)优势
  • 避免类的重复加载
  • 保护程序安全,防止核心API被随意篡改,

例如java.lang包下都是java的核心API,如果我们自定义这样的类则会运行报错:自定义类:java.lang.String 自定义类:java.lang.shkStart

4)劣势

  • 虽然结构清晰,但是顶层的类加载器不能访问底层的类加载器所加载的类。通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出问题。比如在系统类中提供了一个接口,该接口需要在应用中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

4、JVM内存结构

在这里插入图片描述

4.1JVM内存结构之------PC寄存器(线程私有)

简单理解:我们每一条指令都有执行顺序,PC寄存器存储着这些指令对应的地址(即将要执行的指令代码),由执行引擎读取下一条指令。
在这里插入图片描述

它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要它来完成,是唯一一个在Java虚拟机规范中没有规定任何Out of Memory Error情况的区域;
两个常见问题:
1)使用PC寄存器存储字节指令地址有什么用?(为什么用PC寄存器记录当前线程的执行地址呢?)
因为CPU需要不停的切换各个线程,这时候切换回来之后,需要知道从哪开始继续执行。
2)PC寄存器为什么被设定为线程私有?
为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器。

4.2JVM内存结构之------虚拟机栈(先进后出)

简单理解:栈是运行时的单位,而堆是存储的单位。即:栈解决程序的运行问题,即程序如何执行,如何处理数据;堆解决的是数据存储问题,即数据怎么放、放在哪儿。
作用:主管Java程序的运行,它保存方法的局部变量、变量结果,并参与方法的调用和返回。
每个线程在创建时都会创建一个虚拟机栈,其内部是一个个的栈帧,每个栈帧对应着一次方法调用(每个方法对应着各自的一个栈帧)。栈的生命周期和线程一致。

在这里插入图片描述

4.2.1局部变量表

  • 方法嵌套调用的次数由栈的大小决定,一般来说,栈越大,方法嵌套调用的次数越多;
  • 局部变量表中的变量只有在当前方法调用有效,方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁(所以栈不存在GC问题)。
  • 局部变量表的基本单位是Slot(变量槽),存放编译期可知的8种基本类型(存储前会被转换为int)及引用类型、ReturnAddress类型的变量;

4.2.2 操作数栈

  • 主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间;
  • 当一个方法刚开始执行的时候,一个新的栈帧也会被随之创建,这个方法的操作数栈是空的;

栈的优点(特点)

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器;
  • 对于栈来说不存在(GC)垃圾回收问题。

几个栈常问问题:
1、举例栈溢出的情况(StackOverFlowError)
递归 通过-Xss设置栈的大小;
2、调整栈大小,就能保证不出现溢出吗? 不能
3、分配的栈内存越大越好吗? 不是,栈分配的大就会挤占其他模型的空间

4.3JVM内存结构之------本地方法栈(线程私有)

Java虚拟机栈是用于管理Java方法的调用,而本地方法栈适用于管理本地方法的调用,本地方法是使用C语言实现的。

4.3.1 什么是本地方法?
简单的讲,一个Native Method就是一个Java调用非Java代码的接口。使用本地方法最主要的原因是Java应用需要与Java外面的环境交互,本地方法为我们提供简洁的接口,直接调用即可。
当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
并不是所有的JVM都支持本地方法,在Hotspot JVM中,直接将本地方法和虚拟机栈合二为一。

4.4JVM内存结构之------堆(线程共享)

4.4.1堆的核心概述

  • 一个JVM实例只存在一个堆内存(大小可以调节),堆也是Java内存管理的核心区域;
  • Java堆区在JVM启动的时候即被创建,其空间大小也就确定,是JVM管理的最大一块内存空间;
  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(TLAB);
  • 数组和对象永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或数组在堆中的位置。所以,方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除;

4.4.2堆的内存细分

分代的唯一理由就是为了:优化GC性能

Java7及之前堆内存逻辑上分为三块:新生代+老年代+永久区

Java8及之后堆内存逻辑上分为三块:新生代+老年代+元空间;

新生代与老年代内存默认比例是1:2,而在新生代中Eden与两个Survivor空间缺省比例是8:1:1,如下图
在这里插入图片描述

几乎所有的Java对象都是在Eden区被new出来,当对象幸存下来就会,会一步步从Eden----> Survivor1------> Survivor2 ----->tenured,从新生代向老年代迁移,但注意:绝大部分Java对象的销毁都在新生代进行了,可以说新生代中80%的对象都是"朝生夕死"的;
在这里插入图片描述

下面代码可以看jvm堆内存的大小,代码中注释的部分要格外注意,面试及工作中较为常见。

package com.tanhua.sso.xml;


/**
 * 1、设置堆空间大小的参数
 *  -Xms 用来设置堆空间(新生代+老年代)的初始化内存大小
 *    -X 是jvm的运行参数
 *    ms 是memory start
 *  -Xmx 用来设置堆空间(新生代+老年代)的最大内存大小
 *
 *2、默认堆空间的大小
 *   初始内存大小:物理电脑内存大小/64
 *          最大内存大小:物理电脑内存/4
 *
 * 3、手动设置: -Xms600m -Xmx600m
 *       开发中建议将初始堆内存和最大堆内存设置成相同的值。
 *
 */
public class HeapSpaceInitial {

    public static void main(String[] args) {

        //返回Java虚拟机中的堆内存总量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
        System.out.println("-Xms:" + initialMemory + "M");   //-Xms:243M
        System.out.println("-Xmx:" + maxMemory + "M");       //-Xmx:3591M

        System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");//系统内存大小为:15.1875G
        System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G"); //系统内存大小为:14.02734375G
    }
}

4.4.3对象分配过程

每个对象在Eden区被new出来,当Eden区满的时候就会触发GC回收,幸存下来的对象就会分配一个age计数器,放入到Survivor区(Survivor不会触发GC,随着Eden的回收而回收),当age到达阈值15时,就会向老年代迁移。

在这里插入图片描述

总结:

  • 针对幸存者(Survivor)S0、S1区的总结:复制之后有交换,谁空谁是to;
  • 关于垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间收集;

小问:

堆是对象存储的唯一选择吗?

  • 不是,如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,就可能被优化成栈上分配,这样就不需要在堆上分配内存,也就不用进行GC回收了。(如何快速判断是否发生了逃逸分析,就看new的实体对象是否有可能在方法外被调用)
  • 开发中能使用局部变量的,就不要使用在方法外定义;(实用开发技巧)
4.5JVM内存结构之------方法区(线程共享)
  • jdk7以前,习惯把方法区称为永久代,jdk8后,使用元空间(Metaspace)代替了永久代,注意:元空间与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中。(方法区看作是一块独立于Java堆的内存空间)
  • 方法区的大小决定了系统可以保存多少个类;

4.5.1方法区的内部结构

方法区存储已被虚拟机加载的类型信息、常量、静态变量等,在jdk8之后,方法区就从虚拟机的堆空间中抽离,放到了本地内存中(字面量还是放在堆里面,更高效),也就是说此时的元空间(方法区)的大小由本地内存大小决定,之所以将元空间从jvm的内存里分离,也是因为元空间里的数据不轻易被GC回收,可以理解为是一种优化;

1、永久代为什么要被元空间替换?

  • 为永久代设置空间大小是很难确定的,元空间与永久代最大的区别在于元空间不在虚拟机里,而是使用本地内存,大小由本地内存决定;
  • 对永久代调优是很困难的,因为判断一个类不再被使用,Full GC是很耗时间的。

2、StringTable(字面量)为什么要调整?

  • jdk7中将字面量StringTable放到了堆空间中。因为永久代的回收效率低,在full GC时才会触发,而full GC是在永久代和老年代空间都不足时才会触发。这就导致StringTable回收效率不高,而我们开发中大量的字符串被创建,放到堆里面,可以更好的被回收。

在这里插入图片描述
4.5.2 堆、栈、方法区的交互关系

创建一个Person,其具体分配如下图:
在这里插入图片描述
4.5.3方法区的垃圾收集
主要分为两部分:常量池中废弃的常量(没有被任何引用即回收)和不再使用的类型(该类的所有实例不再被使用);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、什么是JVM  JVM是Java Virtual Machine(Java虚拟)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算,是通过在实际的计算上仿真模拟各种计算功能来实现的。Java虚拟包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的器指令执行。  Java语言的一个非重要的特点就是与平台的无关性。而使用Java虚拟是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟在执行字节码时,把字节码解释成具体平台上的器指令执行。这就是Java的能够“一次编译,到处运行”的原因。二、JVM的组成我们先把JVM这个虚拟画出来,如下图所示:从这张图中我们可以看出,JVM是运行在操作系统之上的,它与硬件没有直接的交互,我们再来看JVM由哪些部分组成,如下图所示:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值