10 Star 222 Fork 91

蜗牛201 / interview-reference

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
14.携程-Java高级.md 23.38 KB
一键复制 编辑 Web IDE 原始数据 按行查看 历史
1afdad 提交于 2022-04-03 08:43 . update readme.md

其他互联网大厂面试题

1:阿里巴巴Java面试题

2:阿里云Java面试题-实习生岗

3:腾讯Java面试题-高级

4:字节跳动Java面试题

5:字节跳动Java面试题-大数据方向

6:百度Java面试题

7:蚂蚁金服Java面试题-中级

8:蚂蚁金服Java面试题-高级

9:京东Java面试题-中级

10:拼多多Java面试题-电商部

11:商汤科技Java面试题

12:中软国际Java面试题-初级

13:唯品会Java面试题

14:携程Java面试题-高级

15:软通动力Java面试题


下载链接全部大厂面试题及答案PDF

1.描述一下 JVM 加载 Class 文件的原理机制?

在面试java 工程师的时候,这道题经常被问到,故需特别注意。Java 中的所有类,都需要由类加载器装载到 JVM 中才能运行。类加载器本身也是一个类,而它的工作就是把 class 文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

Java 类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm 中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

Java 的类加载器有三个,对应Java 的三种类:

Bootstrap Loader /负责加载系统类(指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)

ExtClassLoader //负责加载扩展类(就是维承类和实现类)

AppClassLoader //负责加载应用类《程序员自定义的类》

三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完 成呢?为了解决这个问题,Java 采用了委托模型机制。

委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其 Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机 制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到 jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。

我们可以通过这样的代码来获取类加载器:

ClassLoader loader= ClassName.class.getClassLoader();ClassLoader ParentLoader= loader.getParent();

注意一个很重要的问题,就是 Java 在逻辑上并不存在 BootstrapKLoader 的实体!因为它是用 C++编写的,所以打印其内容将会得到 null。

前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:

1.装载:查找和导入 class 文件;

2.连接:

(1)检查∶检查载入的class文件数据的正确性;

(2)准备∶为类的静态变量分配存储空间;

(3)解析∶将符号引用转换成直接引用(这一步是可选的)

3.初始化:初始化静态变量,静态代码块。

这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类构造器也会引发该动作。

2.什么是类加载器?

类加载器是一个用来加载类文件的类。Java 源代码通过 javac 编译器编译成类文件。然后 JVM 来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。

3.类加载器有哪些?

有三种默认使用的类加载器:Bootstrap 类加载器、Extension 类加载器和Application 类加载器。每种类加载器都有设定好从哪里加载类。

Bootstrap 类加载器负责加载 rt.jar 中的 JDK 类文件,它是所有类加载器的父加载 器 。 Bootstrap 类 加 载 器 没 有 任 何 父 类 加 载 器 , 如 果 你 调 用String.class.getClassLoader() , 会返回 null , 任何基于此的代码会抛出NullPointerException 异常。Bootstrap 加载器被称为初始类加载器。

而 Extension 将加载类的请求先委托给它的父加载器,也就是 Bootstrap,如果没有成功加载的话,再从 jre/lib/ext 目录下或者 java.ext.dirs 系统属性定义的目录下加载类。Extension 加载器由sun.misc.Launcher$ExtClassLoader 实现。

第三种默认的加载器就是 Application 类加载器了。

它负责从 classpath 环境变量中加载某些应用相关的类,classpath 环境变量通常由-classpath 或-cp 命令行选项来定义,或者是 JAR 中的 Manifest 的 classpath 属性。Application 类加 载 器 是 Extension 类 加 载 器 的 子 加 载 器 。 通 过sun.misc.Launcher$AppClassLoader 实现。

4.什么是tomcat 类加载机制?

在 tomcat 中类的加载稍有不同,如下图:

当 tomcat 启动时,会创建几种类加载器: 1 Bootstrap 引导类加载器 加载 JVM 启动所需的类,以及标准扩展类(位于 jre/lib/ext 下) 2 System 系统类加载器 加载 tomcat 启动的类, 比如 bootstrap.jar , 通常在 catalina.bat 或者catalina.sh 中指定。位于 CATALINA_HOME/bin 下。

3Common 通用类加载器

加载 tomcat 使用以及应用通用的一些类,位于 CATALINA_HOME/lib 下,比如 servlet-api.jar

4webapp 应用类加载器

每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于WEB-INF/lib 下的 jar 文件中的 class 和 WEB-INF/classes 下的 class 文件。

当应用需要到某个类时,则会按照下面的顺序进行类加载: 1 使用 bootstrap 引导类加载器加载 2使用 system 系统类加载器加载

3使用应用类加载器在 WEB-INF/classes 中加载

4使用应用类加载器在 WEB-INF/lib 中加载

5使用 common 类加载器在 CATALINA_HOME/lib 中加载

5、类加载器双亲委派模型机制?

什么是双亲委派模型(Parent-Delegation Model)?为什么使用双亲委派模型?

JVM 中加载类机制采用的是双亲委派模型,顾名思义,在该模型中,子类加载器收到的加载请求,不会先去处理,而是先把请求委派给父类加载器处理,当父类加载器处理不了时再返回给子类加载器加载;

为什么使用双亲委派模型?

因为安全。使用双亲委派模型来组织类加载器间的关系,能够使类的加载也具有层次关系,这样能够保证核心基础的 Java 类会被根加载器加载,而不会去加载用户自定义的和基础类库相同名字的类,从而保证系统的有序、安全。

6.Java 内存分配?

一、 基本概念

每运行一个 java 程序会产生一个 java 进程,每个 java 进程可能包含一个或者多个线程,每一个Java 进程对应唯一一个 JVM 实例,每一个 JVM 实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。Java 中分配堆内存是自动初始化的,即为一个对象分配内存的时候, 会初始化这个对象中变量。虽然 Java 中所有对象的存储空间都是在堆中分配的, 但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收。

具体的概念:JVM 的内存可分为 3 个区:堆(heap)、栈(stack)和方法区(method, 也叫静态区):

堆区:

存储的全部是对象,每个对象都包含一个与之对应的 class 的信息(class 的目的是得到操作指令) ;

jvm 只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,

只存放对象本身和数组本身; 栈区:

每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用; 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;

栈分为 3 个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令);

方法区(静态区):

被所有的线程共享,方法区包含所有的 class(class 是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和 static 变量。

方法区中包含的都是在整个程序中永远唯一的元素,如 class,static 变量。

二、实例演示AppMain.java

运行该程序时, 首先启动一个 Java 虚拟机进程, 这个进程首先从 classpath 中找到AppMain.class 文件,读取这个文件中的二进制数据,然后把 Appmain 类的类信息存放到运行时数据区的方法区中,这就是 AppMain 类的加载过程。

接着,Java 虚拟机定位到方法区中 AppMain 类的 Main()方法的字节码,开始执行它的指令。这个 main()方法的第一条语句就是:

该语句的执行过程:

1、Java 虚拟机到方法区找到 Sample 类的类型信息,没有找到,因为 Sample 类还没有加载到方法区(这里可以看出,java 中的内部类是单独存在的,而且刚开始的时候不会跟随包含类一起被加载,等到要用的时候才被加载)。Java 虚拟机立马加载 Sample 类,把 Sample 类的类型信息存放在方法区里。

2、Java 虚拟机首先在堆区中为一个新的 Sample 实例分配内存, 并在 Sample 实例的内存中存放一个方法区中存放 Sample 类的类型信息的内存地址。

3、JVM 的进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压 入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。

4、位于“=”前的 Test1 是一个在 main()方法中定义的一个变量(一个 Sample 对象的引用), 因此,它被会添加到了执行 main()方法的主线程的 JAVA 方法调用栈中。而“=”将把这个 test1 变量指向堆区中的 Sample 实例。

5、JVM 在堆区里继续创建另一个Sample 实例,并在 main 方法的方法调用栈中添加一个Test2 变量,该变量指向堆区中刚才创建的 Sample 新实例。

6、JVM 依次执行它们的 printName()方法。当 JAVA 虚拟机执行 test1.printName()方法时,JAVA 虚拟机根据局部变量 test1 持有的引用,定位到堆区中的 Sample 实例,再根据 Sample 实例持有的引用,定位到方法去中 Sample 类的类型信息,从而获得 printName()方法的字节码, 接着执行 printName()方法包含的指令,开始执行。

三、辨析

在 Java 语言里堆(heap)和栈(stack)里的区别 :

栈(stack)与堆(heap)都是 Java 用来在 Ram 中存放数据的地方。与 C++不同,Java 自动管理栈和堆,程序员不能直接地设置栈或堆。 栈的优势是,存取速度比堆要快,仅次于直接位于 CPU 中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享(详见下面的介绍)。 堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

Java 中的 2 种数据类型:

一种是基本类型(primitive types), 共有 8 类,即 int, short, long, byte, float, double, boolean, char(注意,并没有 string 的基本类型)。这种类型的定义是通过诸如 int a = 3; long b = 255L; 的形式来定义的,称为自动变量。自动变量存的是字面值,不是类的实例,即不是类的引用, 这里并没有类的存在。如 int a = 3; 这里的 a 是一个指向 int 类型的引用,指向 3 这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,

程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

栈有一个很重要的特性:存在栈中的数据可以共享。假设我们同时定义: int a = 3; int b = 3; 编译器先处理 int a = 3;首先它会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为 3 的地址,如果没找到,就开辟一个存放 3 这个字面值的地址,然后将 a 指向 3 的地址。接着处理 int b = 3;在创建完 b 的引用变量后,由于在栈中已经有 3 这个字面值,便将 b 直接指向 3 的地址。这样,就出现了 a 与 b 同时均指向 3 的情况。

这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一 个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变 化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着 改变的情况。如上例,我们定义完 a 与 b 的值后,再令 a=4;那么,b 不会等于 4,还是等于 3。在编译器内部,遇到 a=4;时,它就会重新搜索栈中是否有 4 的字面值,如果没有, 重新开辟地址存放 4 的值;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值。 另一种是包装类数据,如 Integer, String, Double 等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java 用 new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

7.Java 堆的结构是什么样子的?

JVM 的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在 JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。

堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死 亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些 对象回收掉之前,他们会一直占据堆内存空间。

永久代是用于存放静态文件,如 Java 类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些 class,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类,永久代中一般包含:

类的方法(字节码…) 类名(Sring 对象)

.class 文件读到的常量信息

class 对象相关的对象列表和类型列表 (e.g., 方法对象的 array).

JVM 创建的内部对象

JIT 编译器优化用的信息

虚拟机中的共划分为三个代:

年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是 Java 类的类信息,与垃圾收集要收集的 Java 对象关系不大。年轻代和年老代的划分是对垃 圾收集影响比较大的。

年轻代:

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生 命周期短的对象。年轻代分三个区。一个 Eden 区,两个 Survivor 区(一般而言)。大部分对象在 Eden 区中生成。当 Eden 区满时,还存活的对象将被复制到 Survivor 区(两个中的一个),当这个 Survivor 区满时,此区的存活对象将被复制到另外一个 Survivor 区,当这个 Survivor 去也满了的时候,从第一个 Survivor 区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor 的两个区是对称的,没先后关系,所以同一个区中可能同时存在从 Eden 复制过来对象,和从前一个 Survivor 复制过来的对象,而复制到年老区的只有从第一个 Survivor 去过来的对象。而且,Survivor 区总有一个是空的。同时,根据程序需要,Survivor 区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代:

在年轻代中经历了 N 次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

持久代:

用于存放静态文件,如今 Java 类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些 class,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

持久代大小通过-XX:MaxPermSize=进行设置。

注意:

JDK1.8 中,永久代已经从java 堆中移除,String 直接存放在堆中,类的元数据存储在meta space中,meta space 占用外部内存,不占用堆内存。

可以说,在 java8 的新版本中,持久代已经更名为了元空间(meta space)。

8.简述各个版本内存区域的变化?

参考:https://blog.csdn.net/rainnnbow/article/details/50541079

9.说说各个区域的作用?

1、运行时数据区域

运行时数据区域包括方法区、虚拟机栈、本地方法栈、堆、程序计数器。其中方法区和堆是 所有线程共享的数据区,其他的是线程隔离的数据区。

1.1、程序计数器

程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器,确定下一条需要执行的字节码指令。java 的多线程是通 过线程轮流切换并分配处理器执行时间的方式来实现的,在任何确定的一个时刻,一个处理器只会执行一条线程中的指 令。为了线程切换之后能恢复到正确的执行位 置,每个线程都需要有一个独立的程序计数器,各个线程之间的计数器互不影响。如果线程正在执行的是一个 java 方法,则计数器记录的是正在执行的虚拟机字 节码指令的地址,如果正在执行的是 native 方法,则计数器值为空。

1.2、java 虚拟机栈

java 虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 java 方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用 于存在局部变量表、操作栈、动态链接、方法出口等信息。通过所说的栈是局部变量表,即与对象内存分配关系最密切的内 存区域。局部变量表的内存空间在编译期 间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是确定的,在运行期不会改变。

java 虚拟机栈有两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,则抛弃StackOverflowError 异常;如果虚拟机栈可以动态扩展的,当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。

1.3、本地方法栈

本地方法栈与虚拟机栈所发挥的作用是相似的,区别在于虚拟机栈为虚拟机执行 java 方法的服务,本地方法栈则是为虚拟机使用到 native 方法服务。

1.4、java 堆

java 堆是虚拟机所管理的内存中最大的一块,是虚拟机启动是创建的能被所有线程共享的一块内存区域。java 堆的唯一目的就是存放对象实例,几乎 所有的对象实例和数组都在这里分配内存(随着 JTI 编译器的发展,在栈上也有可能分配)。java 堆是垃圾收集器管理的主要区域,在物理上可以使不连续的 内存空间,但在逻辑上是联系的。

如果再堆中没有内存完成实例的分配, 并且堆也无法在扩展的时候, 将会抛出OutOfMemoryError 异常。

1.5、方法区

方法区也是线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这块区域很少进行垃圾回收,甚 至可以不实现垃圾收集, 主要是针对常量池的回收和对类型的卸载。当方法区无法分配内存的时候, 将抛出OutOfMemoryError 异常。

Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池, 用于存放编译期的各种字面量和符号引用。并非预置入 Class 文件中的常量才能进入常量池,运行期间也可能将新的常量放入池中,开发中用的比较多的是 String 类的 intern()方法。

2、例子解析

假设这句代码出现在方法体中,那么 Object obj 将会反映到 java 栈的局部变量表中,作为一个 reference 类型数据出现,new Object()将会反映到 java 堆中,形成一块存储了 Object 类型的实例数据的结构化内存,此对象类型数据,如对象类型、父类、实现的接口、方法 等信息存储在方法区。

10.Java 中会存在内存泄漏吗,简述一下?

理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。例如 Hibernate 的 Session(一级缓存) 中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。

11.Java 类加载过程?

12.什么是 GC? 为什么要有 GC?

13.简述一下 Java 垃圾回收机制?

14.如何判断一个对象是否存活?

15.垃圾回收的优点和原理,并考虑 2 种回收机制? 基本原理是什么?

16.深拷贝和浅拷贝?

17.什么是分布式垃圾回收(DGC)?它是如何工作的?

下载链接博主已将以上这些大厂面试题整理成了一个面试手册,是PDF版的

1
https://gitee.com/woniu201/interview-reference.git
git@gitee.com:woniu201/interview-reference.git
woniu201
interview-reference
interview-reference
master

搜索帮助

14c37bed 8189591 565d56ea 8189591