JVM常见面试题

JVM常见面试题

    • 前言
    • [1. Java文件编译的过程](#1. Java文件编译的过程)
    • [2. Jdk和Jre和JVM的区别](#2. Jdk和Jre和JVM的区别)
    • 3.说一下JVM由那些部分组成,运行流程是什么?
    • [4. 说一下 JVM 运行时数据区](#4. 说一下 JVM 运行时数据区)
    • [5. 详细的介绍下程序计数器?(重点理解)](#5. 详细的介绍下程序计数器?(重点理解))
    • [6. 详细介绍下Java虚拟机栈?(重点理解)](#6. 详细介绍下Java虚拟机栈?(重点理解))
    • [7. 你能给我详细的介绍Java堆吗?(重点理解)](#7. 你能给我详细的介绍Java堆吗?(重点理解))
    • [8. 能不能解释一下本地方法栈?](#8. 能不能解释一下本地方法栈?)
    • [9. 能不能解释一下方法区(重点理解)](#9. 能不能解释一下方法区(重点理解))
    • [10. 什么是JVM字节码执行引擎](#10. 什么是JVM字节码执行引擎)
    • [11. 你听过直接内存吗?](#11. 你听过直接内存吗?)
    • [12. 堆栈的区别是什么?](#12. 堆栈的区别是什么?)
    • 13.深拷贝和浅拷贝
    • [14. Java会存在内存泄漏吗?请说明为什么?](#14. Java会存在内存泄漏吗?请说明为什么?)
    • [15. 简述Java垃圾回收机制](#15. 简述Java垃圾回收机制)
    • [16. GC是什么?为什么要GC](#16. GC是什么?为什么要GC)
    • [17. 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?](#17. 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?)
    • [18. JVM中都有哪些引用类型?](#18. JVM中都有哪些引用类型?)
    • [20. 怎么判断对象是否可以被回收?](#20. 怎么判断对象是否可以被回收?)
    • [21. JVM垃圾回收算法有哪些?](#21. JVM垃圾回收算法有哪些?)
    • [22. 讲一下新生代、老年代、永久代的区别](#22. 讲一下新生代、老年代、永久代的区别)
    • [23. Minor GC、Major GC、Full GC是什么](#23. Minor GC、Major GC、Full GC是什么)
    • [24. Minor GC、Major GC、Full GC区别及触发条件](#24. Minor GC、Major GC、Full GC区别及触发条件)
    • [25. 为什么新生代要分Eden和两个Survivor 区域?](#25. 为什么新生代要分Eden和两个Survivor 区域?)
    • [26. Java堆老年代( Old )和新生代( Young )的默认比例?](#26. Java堆老年代( Old )和新生代( Young )的默认比例?)
    • [27. 为什么要这样分代:](#27. 为什么要这样分代:)
    • [28. 说一下JVM有哪些垃圾回收器?](#28. 说一下JVM有哪些垃圾回收器?)
    • [29. 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?](#29. 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?)
    • [30. 简述分代垃圾回收器是怎么工作的?](#30. 简述分代垃圾回收器是怎么工作的?)
    • [31. 简述java内存分配与回收策率以及Minor GC和Major GC](#31. 简述java内存分配与回收策率以及Minor GC和Major GC)
    • [32. 对象优先在Eden区分配](#32. 对象优先在Eden区分配)
    • [33. 长期存活对象将进入老年代](#33. 长期存活对象将进入老年代)
    • [34. 简述java类加载机制?](#34. 简述java类加载机制?)
    • [35. 类加载的机制及过程](#35. 类加载的机制及过程)
    • [36. 描述一下JVM加载Class文件的原理机制](#36. 描述一下JVM加载Class文件的原理机制)
    • [37. 什么是类加载器,类加载器有哪些?](#37. 什么是类加载器,类加载器有哪些?)
    • [38. 说一下类装载的执行过程?](#38. 说一下类装载的执行过程?)
    • [39. 什么是双亲委派模型,怎么打破双亲委派模型??](#39. 什么是双亲委派模型,怎么打破双亲委派模型??)
    • [40. 常用的JVM调优的参数都有哪些?](#40. 常用的JVM调优的参数都有哪些?)
    • [41. JVM的GC收集器设置](#41. JVM的GC收集器设置)
    • [42. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。](#42. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。)
    • [43. 怎么打出线程栈信息](#43. 怎么打出线程栈信息)
    • [43. 怎么打出线程栈信息](#43. 怎么打出线程栈信息)
    • 小结

前言

本片文章主要总结了JVM常见的面试题,大家可以根据目录选择自己没有掌握的,也可以全部看一遍,后面一些其他技术的面试题也会陆陆续续的发布,为大多面试者提供一个复习的平台。感兴趣的铁铁可以关注下本专栏;在本系列文章中有我自己在思考题目的时候,写的简记,或者我认为重点的都加粗进行了处理,给大家提供借鉴,题目大都是从各个地方总结而来,后面有新题目也会不断的进行扩充。如果对文章中的题目存在一些独特的见解或者发现了文章中的一些错误,请各位在评论区中指出,希望和大家一起维护,学习。最后,文章创作不易,各位铁铁能否一键三连(点赞、关注、收藏)给我一点动力,创造其他好的文章。祝各位铁铁能面进自己想要去的公司,加油!

1. Java文件编译的过程

  1. 程序员编写的.java文件
  2. javac编译成字节码文件.class:(为什么编译成class文件,因为JVM只认识.class文件)
  3. 在由JVM编译成电脑认识的文件 (对于电脑系统来说 文件代表一切)

简记

​ Hello.java -- javac --> hello.class --JVM -->-010101...

2. Jdk和Jre和JVM的区别

Jdk包括了Jre和Jvm,Jre包括了Jvm

Jdk是我们编写代码使用的开发工具包

Jre 是Java的运行时环境,他大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库

Jvm俗称Java虚拟机,他是java运行环境的一部分,JVM是一个虚拟的计算机,它接收到字节码(编译后的Java程序),然后解释或编译执行。

3.说一下JVM由那些部分组成,运行流程是什么?

JVM包含两个子系统和两个组件:

  • 两个子系统为Class loader(类装载 )、Execution engine(执行引擎);

  • 两个组件为Runtime data area(运行时数据区 )、Native Interface(本地接口)。

(1) Class loader(类装载):根据给定的全类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。

(2) Execution engine(执行引擎):执行classes中的指令。

(3) Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。

(4) Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

流程 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到

内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一

套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎

(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要

调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

简记

组成:类加载器 ,运行时数据区,执行引擎,本地库接口。

流程:hello.java --- javac --- >hello.class 字节码 -- 类加载器 -- > 内存(运行时数据区的方法区)

--执行引擎--> 0101-->操作系统底层;在这个过程中通过调用本地库接口来让操作系统完成相关操作。

4. 说一下 JVM 运行时数据区

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:

程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;为什么要线程计数器?因为线程是不具备记忆功能。

Java 虚拟机栈 (Java Virtual Machine Stacks):每个方法在执行的同时都会在Java 虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息;栈帧就是Java虚拟机栈中的下一个单位。

本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码

Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;

方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

简记

运行时数据区主要包括程序计数器,java虚拟机栈,本地方法栈,堆,方法区

程序计数器 (每个线程独享):记录当前线程执行到哪个地方,当被中断,知道从哪里继续执行。

java虚拟机栈 (独享 ):每个方法执行时会在栈中开辟一块栈帧,用来存放方法执行过程中需要的信息,主要有局部变量表,操作数栈,动态链接,方法出口

本地方法栈 (独享):为虚拟机提供本地方法,本地方法即用native关键字修饰的方法,由c/c++进行编写。

(共享 ):用来存放大部分实例对象

方法区 (共享):用来存放虚拟机已加载的类信息、常量、静态变量即时编译的代码等数据。

5. 详细的介绍下程序计数器?(重点理解)

  1. 程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)

  2. 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为"线程私有"的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。

例子:在java中最小的执行单位是线程,线程是要执行指令的,执行的指令终操作的就是我们的电脑,就是 CPU。在CPU上面去运行,操作系统会根据调度策略来决定哪个线程去CPU上执行,所以存在线程切换的场景。

简记:保存当前线程所正在执行的字节码指令的地址(行号),确保线程切换后能恢复到正确位置的关键。

程序计数器:

6. 详细介绍下Java虚拟机栈?(重点理解)

  1. Java虚拟机是线程私有的,它的生命周期和线程相同。
  2. 虚拟机栈描述的是Java方法执行的内存模型: 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

虚拟机栈中是有单位的,单位就是栈帧 ,一个方法一个栈帧 。一个栈帧中他又要存储方法需要的信息,例如局部变量表,操作数栈,动态链接,出口等。

栈帧:

  1. 局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)

  2. 操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去

  3. 动态链接:假如方法中有个 service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方。

  4. 出口:出口正常的话就是return 不正常的话就是抛出异常。

相关问题

一个方法调用另一个方法,会创建很多栈帧吗?

会。如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是有顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者上面,因为栈是FILO。

栈指向堆是什么意思?

栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个对象引用地址

递归的调用自己会创建很多栈帧吗?

递归的话也会创建多个栈帧,就是在栈中一直从下往上排,所以我们在用递归的时候,可能会创建了大量的栈帧导致栈溢出。

简记

虚拟机栈是线程私有的,跟虚拟机共存亡,每个方法都会在虚拟机栈上创建一个栈帧,栈帧存储方法执行过程中的信息,例如,局部变量表,操作数栈,动态链接,方法出口等。局部变量表用来存储临时8个基本数据类型、对象引用地址、returnAddress类型;操作数栈存放操作数;动态链接存放方法引用的地址,方法出口,则就是方法执行结果。

7. 你能给我详细的介绍Java堆吗?(重点理解)

java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,是被所有线程共享 的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。java堆是垃圾收集器管理的主要区域,因此也被成为"GC堆"。从内存回收角度来看java堆可分为:新生代和老生代。从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

简记

Java堆是所有线程共享的,用来存放实例对象和数组。是垃圾回收管理的主要区域,通过各种垃圾回收算法,实现内存的合理回收与分配。当堆内存内有内存分配给对象实例时,就会报OOM 堆内存溢出异常。

8. 能不能解释一下本地方法栈?

  1. 本地方法栈很好理解,他跟栈很像,只不过方法上带了 native 关键字的栈
  2. 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法
  3. native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。

简记

本地方法栈就是存放方法上带有native关键字的区域,在虚拟机执行相关操作时调用.大部分都是c/c++实现的代码。

9. 能不能解释一下方法区(重点理解)

  1. 方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  2. 它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

简记

方法区是所有线程共享的区域,用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

10. 什么是JVM字节码执行引擎

虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。

"虚拟机"是一个相对于"物理机"的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎- 编译成机器码后才可在物理机上执行。

简记

**字节码执行引擎就是负责将字节码转化为机器码的核心组件。**是java程序能够在物理机上运行的保证。

11. 你听过直接内存吗?

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现。

简记

直接内存又称堆外内存,是虚拟机内存之外的内存,直接内存不会进行垃圾回收,当我们使用的时候要手动进行回收,不然就会导致内存泄漏。

12. 堆栈的区别是什么?

注意:

静态变量放在方法区 ;静态的对象还是放在堆。

13.深拷贝和浅拷贝

浅拷贝(shallowCopy)增加了一个指针指向已存在的内存地址.

深拷贝(deepCopy)增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,

浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。

深复制 :在计算机中开辟一块新的内存地址用于存放复制的对象。

14. Java会存在内存泄漏吗?请说明为什么?

​ 内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。但是,即使这样,Java也还是存在着内存泄漏 的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露, 尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收 ,这就是java中内存泄露的发生场景。

15. 简述Java垃圾回收机制

​ 在java中,程序员是不需要主动的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

16. GC是什么?为什么要GC

GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。

17. 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。

程序员可以手动执行System.gc(),通知GC运行,但是并不保证GC一定会执行

18. JVM中都有哪些引用类型?

1)强引用

我们平时new了一个对象就是强引用,例如 Object obj = new Object();即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

2)软引用

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。

复制代码
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用

用处: 软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

如下代码:

复制代码
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
	rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}else{
    prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
    sr = new SoftReference(prev); // 重新构建
}

3)弱引用

具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

复制代码
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str=null;
等价于
str = null;
System.gc();

4)虚引用

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动

20. 怎么判断对象是否可以被回收?

垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是存活的,是不可以被回收的;哪些对象已经死掉了,需要被回收。

一般有两种方法来判断:

引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数-1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;(这个已经淘汰了)

可达性分析算法 :从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。(市场上用的非常广泛)

21. JVM垃圾回收算法有哪些?

标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。

复制算法 :按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

(1) 标记-清除算法:标记无用对象,然后进行清除回收。

标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:

标记阶段:标记出可以回收的对象。

清除阶段:回收被标记的对象所占用的空间。

标记-清除算法是垃圾回收算法的基础,垃圾收集算法都是在此算法的基础上进行改进的。

优点:实现简单,不需要对象进行移动。

缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。

标记-清除算法的执行的过程如下图所示。

(2) 复制算法

为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。

优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。

缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

复制算法的执行过程如下图所示

(3) 标记-整理算法:

在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。

优点:解决了标记-清理算法存在的内存碎片问题。

缺点:仍需要进行局部对象移动,一定程度上降低了效率。

标记-整理算法的执行过程如下图所示

(4) 分代收集算法:

当前商业虚拟机都采用 分代收集 的垃圾收集算法。分代收集算法,顾名思义是根据对象的 存活周

期 将内存划分为几块。一般包括 年轻代 、 老年代 和 永久代 如图所示:

简记

主要有四种垃圾回收算法,分别是标记清除算法,复制算法,标记整理算法,分代收集算法。

标记-清除算法:即先标记无用对象,然后清除;操作简单但是会产生大量的内存碎片。

复制算法:内存一分为二,只用其中一份,垃圾回收的时候将存活对象复制到另外一个上,对当前进行回收。无内存碎片,但是内存利用率低且大对象会反复复制,降低性能。

标记-整理算法:先标记,在将存活对象整理到一端,对另一端进行回收。解决了内存碎片问题,但是复制大对象还是会影响性能。

分代收集算法:对内存分区,对不同区采用不同算法,通常对年轻代采用复制,对老年代采用复制整理。

22. 讲一下新生代、老年代、永久代的区别

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。而新生代 ( Young )又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。新生代 中一般保存新出现的对象,所以每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了 复制 算法 ,只需要付出少量存活对象的复制成本就可以完成收集。老年代中一般保存存活了很久的对象,他们存活率高、没有额外空间对它进行分配担保,就必须采用 "标记-清理"或者"标记-整理" 算法。永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。

23. Minor GC、Major GC、Full GC是什么

  1. Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾)

  2. Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。(可采用标记清除法和标记整理法)

  3. Full GC是清理整个堆空间,包括年轻代和老年代

24. Minor GC、Major GC、Full GC区别及触发条件

Minor GC 触发条件:

  1. eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。

  2. 新创建的对象大小 > Eden所剩空间时,触发Minor GC

Major GC和Full GC触发条件: Major GC通常是跟full GC是等价的

  1. 每次晋升到老年代的对象平均大小>老年代剩余空间
  2. MinorGC后存活的对象超过了老年代剩余空间
  3. 永久代空间不足
  4. 执行System.gc()
  5. CMS GC异常
  6. 堆内存分配很大的对象

25. 为什么新生代要分Eden和两个Survivor 区域?

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。

Survivor的存在意义,就是减少被送到老年代的对象 ,进而减少Full GC的发生 ,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次MinorGC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

26. Java堆老年代( Old )和新生代( Young )的默认比例?

新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 --XX:NewRatio来指定 ),

其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域 ,Edem 和俩个Survivor 区域比例是 = 8 : 1 : 1 ( 可以通过参数 --XX:SurvivorRatio 来设定 ),但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

27. 为什么要这样分代:

其实主要原因就是可以根据各个年代的特点进行对象分区存储,更便于回收,采用最适当的收集算法

新生代中,每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。

新生代又分为Eden和Survivor (From与To,这里简称一个区)两个区。加上老年代就这三个区。

数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC,。如果对象经过一次Minor-GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。

28. 说一下JVM有哪些垃圾回收器?

用于回收新生代的收集器包括Serial、PraNew、ParallelScavenge,

回收老年代的收集器包括Serial Old、Parallel Old、CMS,

还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。

Serial收集器复制 算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

ParNew收集器 (复制 算法): 新生代收并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

Parallel Scavenge收集器 (复制 算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。

吞吐量= 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;

Serial Old收集器 (标记-整理 算法): 老年代单线程收集器,Serial收集器的老年代版本;

Parallel Old收集器 (标记-整理 算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

CMS(Concurrent Mark Sweep)收集器标记-清除 算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

G1(Garbage First)收集器 ( 标记整理 + 复制算法来回收垃圾 ): Java堆并行 收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于"标记-整理"算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

29. 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?

新生代回收器:Serial、ParNew、Parallel Scavenge

老年代回收器:Serial Old、Parallel Old、CMS

整堆回收器:G1

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

30. 简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

把 Eden + From Survivor 存活的对象放入 To Survivor 区;

清空 Eden 和 From Survivor 分区;

From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变From Survivor。

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

大对象也会直接进入老生代。老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。

以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

31. 简述java内存分配与回收策率以及Minor GC和Major GC

对象的内存分配通常是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种「普世」规则。

  • 对象优先在 Eden 区分配
  • 大对象直接进入老年代
  • 长期存活对象将进入老年代

32. 对象优先在Eden区分配

多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间 进行分配时,虚拟机将会发起一次 Minor GC 。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。

这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。

Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;

Major GC/Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。

33. 长期存活对象将进入老年代

虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。

34. 简述java类加载机制?

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可

以被虚拟机直接使用的java类型。

35. 类加载的机制及过程

程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。

1、加载

加载指的是将类的class文件读入到内存 ,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。类加载器可以从不同来源加载类的二进制数据,比如:本地Class文件、Jar包Class文件、网络Class文件等等等。类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口。

2、连接过程

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态之中)。类连接又可分为如下3个阶段。

​ 1. 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。主要验证是否符合Class文件格式规范,并且 是否能被当前的虚拟机加载处理。

  1. 准备正式为类变量(static变量)分配内存并设置类变量初始值 的阶段,这些内存都将在方法区中进行分配

  2. 解析 :虚拟机常量池的符号引用替换为字节引用过程

3、初始化

初始化阶段是执行类构造器 () 方法的过程。类构造器 ()方法是由编译器自动收藏类中的 所有类变量的赋值动作和静态语句块(static块)中的语句合并产生,代码从上往下执行。当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化虚拟机会保证一个类的 () 方法在多线程环境中被正确加锁和同步

初始化的总结就是:对类的静态变量初始化为指定的值,执行静态代码块(static{}包裹的就是静态代码块)。

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

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

类装载方式,有两种 :

1**.隐式装载**, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,

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

37. 什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

  • 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库。(C++实现)
  • **扩展类加载器:**负责加载支撑JVM运行的位于JRE的lib目录下的ext拓展目录中的JAR包。
  • **应用程序类加载器:**负责加载ClassPath路径下的类包,也就是自己写的类。
  • **自定义加载器:**负责加载用户自定义路径下的类包。

38. 说一下类装载的执行过程?

类装载分为以下 5 个步骤:

加载:根据查找路径找到相应的 class 文件然后导入;

验证:检查加载的 class 文件的正确性;

准备:给类中的静态变量分配内存空间;

解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;

初始化:对静态变量和静态代码块执行初始化工作。

39. 什么是双亲委派模型,怎么打破双亲委派模型??

在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

总结就是: 当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加

载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

打破双亲委派机制不仅要继承ClassLoader类,还要重写loadClass和findClass方法

40. 常用的JVM调优的参数都有哪些?

复制代码
#常用的设置
-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
-Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
-XX:NewSize=n 设置年轻代初始化大小大小
-XX:MaxNewSize=n 设置年轻代最大值
-XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4
-XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8
-Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
-XX:ThreadStackSize=n 线程堆栈大小
-XX:PermSize=n 设置持久代初始值
-XX:MaxPermSize=n 设置持久代大小
-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。

#下面是一些不常用的
-XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
-XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用

41. JVM的GC收集器设置

复制代码
-xx:+Use xxx GC      xxx 代表垃圾收集器名称
-XX:+UseSerialGC:设置串行收集器,年轻带收集器
-XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再设置此值。
-XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量
-XX:+UseParallelOldGC:设置并行年老代收集器,JDK6.0 支持对年老代并行收集。
-XX:+UseConcMarkSweepGC:设置年老代并发收集器
-XX:+UseG1GC:设置 G1 收集器,JDK1.9默认垃圾收集器

42. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。

思路: 先画出Java内存模型图,结合例子volatile ,说明什么是重排序,内存屏障,最好能给面试官写

我的答案:

1)Java内存模型图

Java内存模型规定了所有的变量都存储在主内存 中,每条线程还有自己的工作内存 ,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中 进行,而不能直接读写主内存 。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

2)指令重排序

在这里,先看一段代码

复制代码
public class PossibleReordering {
	static int x = 0, y = 0;
	static int a = 0, b = 0;
	public static void main(String[] args) throws InterruptedException {
	Thread one = new Thread(new Runnable() { 
        public void run() { 
        a = 1; 
        x = b; 
		}
	});
	Thread other = new Thread(new Runnable() { 
        public void run() { 
            b = 1; 
            y = a; 
        }
	}); 
    one.start();
    other.start(); 
    one.join();
    other.join();
    System.out.println("(" +x + "," + y + ")");
}

运行结果可能为(1,0)、(0,1)或(1,1),也可能是(0,0)。因为,在实际运行时,代码指令可能并不是严格按照代码语句顺序执行的。大多数现代微处理器都会采用将指令乱序执行 (out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。通过乱序执行的技术,处理器可以大大提高执行效率。而这就是指令重排

3)内存屏障

内存屏障,也叫内存栅栏,是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

4)happen-before原则

单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。

volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。

happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。

线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。

线程中断的happen-before原则 :对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。

线程终结的happen-before原则: 线程中的所有操作都happen-before线程的终止检测。

对象创建的happen-before原则: 一个对象的初始化完成先于他的finalize方法调用。

43. 怎么打出线程栈信息

思路: 可以说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。

我的答案:

输入jps,获得进程号。

top -Hp pid 获取本进程中所有线程的CPU耗时性能

jstack pid命令查看当前java进程的堆栈状态

或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。

可以使用fastthread 堆栈定位,fastthread.io/

-XX:+UseParallelGC

-XX:ParallelGCThreads=20

-XX:+UseConcMarkSweepGC

-XX:CMSFullGCsBeforeCompaction=5

-XX:+UseCMSCompactAtFullCollection:

-XX:+PrintGC

-XX:+PrintGCDetails

操作。

线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。

线程中断的happen-before原则 :对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。

线程终结的happen-before原则: 线程中的所有操作都happen-before线程的终止检测。

对象创建的happen-before原则: 一个对象的初始化完成先于他的finalize方法调用。

43. 怎么打出线程栈信息

思路: 可以说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。

我的答案:

输入jps,获得进程号。

top -Hp pid 获取本进程中所有线程的CPU耗时性能

jstack pid命令查看当前java进程的堆栈状态

或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。

可以使用fastthread 堆栈定位,fastthread.io/

-XX:+UseParallelGC

-XX:ParallelGCThreads=20

-XX:+UseConcMarkSweepGC

-XX:CMSFullGCsBeforeCompaction=5

-XX:+UseCMSCompactAtFullCollection:

-XX:+PrintGC

-XX:+PrintGCDetails

小结

能够看完这篇文章,就已经非常棒了,剩下就是巩固和复习了,要发现底层原理,才能更好熟记于心哦。最后记得给我一键三连(点赞关注收藏)哦!/拜托拜托 /

祝各位能够被心仪的公司录取!

相关推荐
安之若素^6 分钟前
启用不安全的HTTP方法
java·开发语言
ruanjiananquan9912 分钟前
c,c++语言的栈内存、堆内存及任意读写内存
java·c语言·c++
chuanauc39 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴1 小时前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao1 小时前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野7 小时前
【Java|集合类】list遍历的6种方式
java·python·list