公众号(五分钟学大数据)已推出大数据面试系列文章—五分钟小面试,此系列文章将会深刻研究各大厂笔面试真题,并根据笔面试题扩展相关的知识点,助力你们都可以成功入职大厂!java
大数据笔面试系列文章分为两种类型:混合型(即一篇文章中会有多个框架的知识点—融会贯通);专项型(一篇文章针对某个框架进行深刻解析—专项演练)。程序员
此篇文章为系列文章的第二篇(JVM专项)面试
答:算法
由于这块内容太多了,许多小伙伴可能记不住这么多,因此下面的答案分为简答和精答。编程
JVM 运行时内存共分为程序计数器,Java虚拟机栈,本地方法栈,堆,方法区五个部分:数组
注:JVM调优主要就是优化 Heap 堆 和 Method Area 方法区缓存
简答: 每一个线程都有一个程序计算器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个很是小的内存空间,几乎能够忽略不记。安全
精答:占据一块较小的内存空间,能够看作当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都须要依赖这个计数器来完成。服务器
因为jvm的多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器都只会执行一条线程中的指令。所以将来线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为“线程私有”的内存。网络
若是线程正在执行的是一个Java方法,这个计数器记录的则是正在执行的虚拟机字节码指令的地址;
若是正在执行的是Native方法,这个计数器则为空(undefined)。
此内存区域是惟一一个在Java虚拟机规范中没有规定任何OutOfMemoryError状况的区域。
简答:主管Java程序的运行,在线程建立时建立,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来讲不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
精答:线程私有,生命周期和线程相同,虚拟机栈描述的是Java方法执行的内存模型,每一个方法在执行的同时都会建立一个栈帧用于存储局部变量表,操做数栈,动态连接,方法出口等信息。每个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了编译期可知的各类基本类型数据(boolean、byte、char、short、int、float、long、double)、对象引用、returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量表空间(slot),其他的数据类型只占用1个。局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法所须要在栈帧中分配多大的局部变量空间是彻底肯定的,在方法运行期间不会改变局部变量表的大小。
在Java虚拟机规范中,对此区域规定了两种异常情况:若是线程请求的栈深度大于虚拟机所容许的深度,将会抛出Stack OverflowError异常;若是虚拟机栈能够动态扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常。
简答:本地方法栈为虚拟机中使用到的native方法服务,native方法做用是融合不一样的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候C/C++横行的时候,要想立足,必须有调用C/C++程序,因而就在内存中专门开辟了一块区域处理标记为native的代码。
精答:本地方法栈与虚拟机栈所发挥的做用很是类似,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机中使用到的native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并无强制规定,所以具体的虚拟机能够自由实现它。甚至有的虚拟机直接把本地方法栈和虚拟机栈合二为一,与虚拟机栈同样也会抛出Stack OverflowError异常和OutOfMemoryError异常。
简答:堆这块区域是JVM中最大的,应用的对象和数据都是存在这个区域,这块区域也是线程共享的,也是 gc 主要的回收区,一个 JVM 实例只存在一个堆类存。堆内存的大小是能够调节的。
精答:对于大多数应用来讲,堆空间是jvm内存中最大的一块。Java堆是被全部线程共享,虚拟机启动时建立,此内存区域惟一的目的就是存放对象实例,几乎全部的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:全部的对象实例以及数组都要在堆上分配,可是随着JIT编译器的发展和逃逸分析技术逐渐成熟,栈上分配,标量替换优化技术将会致使一些微妙的变化发生,全部的对象都分配在堆上也就变得不那么绝对了。
Java堆是垃圾收集器管理的主要区域,所以不少时候也被称为“GC堆”。从内存回收角度看,因为如今收集器基本都采用分代收集算法,因此Java堆还能够细分为:新生代和老年代;再细致一点的有Eden空间,From Survivor空间,To Survivor空间等。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。不过不管如何划分,都与存放内容无关,不管哪一个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。(若是在堆中没有内存完成实例分配,而且堆也没法再扩展时,将会抛出OutOfMemoryError异常。)
简答:和堆同样全部线程共享,主要用于存储已被jvm加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
精答:方法区是被全部线程共享,全部字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,全部定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量,常量,类信息(构造方法/接口定义),运行时常量池存在方法区中;可是实例变量存在堆内存中,和方法区无关。
在JDK1.7发布的HotSpot中,已经把字符串常量池移除方法区了。
简答:运行时常量池是方法区的一部分。用于存放编译期生成的各类字面量和符号引用,它的重要特性是动态性,即Java语言并不要求常量必定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。
精答:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池中存放。
Java虚拟机对class文件每一部分的格式都有严格规定,每个字节用于存储哪一种数据都必须符合规范才会被jvm承认。但对于运行时常量池,Java虚拟机规范没作任何细节要求。
运行时常量池有个重要特性是动态性,Java语言不要求常量必定只在编译期才能产生,也就是并不是预置入class文件中常量池的内容才能进入方法区的运行时常量池,运行期间也有可能将新的常量放入池中,这种特性使用最多的是String类的intern()方法。
既然运行时常量池是方法区的一部分,天然受到方法区内存的限制。当常量池没法再申请到内存时会抛出outOfMemeryError异常。
jdk 1.8 同 jdk 1.7 比,最大的差异就是:元数据区取代了永久代。元空间的本质和永久代相似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
答:
简答:类加载过程便是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。分为五个步骤:加载 -> 验证 -> 准备 -> 解析 -> 初始化。加载:将外部的 .class 文件加载到Java虚拟机中;验证:确保加载进来的 calss 文件包含的额信息符合 Java 虚拟机的要求;准备:为类变量分配内存,设置类变量的初始值;解析:将常量池内的符号引用 转为 直接引用;初始化:初始化类变量和静态代码块。
精答:前方预警,内容较长,作好准备!
一个Java文件从编码完成到最终执行,通常主要包括两个过程:编译、运行
编译:即把咱们写好的java文件,经过javac命令编译成字节码,也就是咱们常说的.class文件。
运行:则是把编译生成的.class文件交给Java虚拟机(JVM)执行。
而咱们所说的类加载过程便是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
举个简单的例子来讲,JVM在执行某段代码时,遇到了class A, 然而此时内存中并无class A的相关信息,因而JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是咱们所说的类加载过程。
因而可知,JVM不是一开始就把全部的类都加载进内存中,而是只有第一次遇到某个须要运行的类时才会加载,且只加载一次。
类加载的过程主要分为三个部分:加载、连接、初始化。
而连接又能够细分为三个小部分:验证、准备、解析。
简单来讲,加载指的是把class字节码文件从各个来源经过类加载器装载入内存中。
这里有两个重点:
字节码来源:通常的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
类加载器:通常包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
注:为何会有自定义类加载器?
一方面是因为java代码很容易被反编译,若是须要对本身的代码加密的话,能够对编译后的代码进行加密,而后再经过实现本身的自定义类加载器进行解密,最后再加载。
另外一方面也有可能从非标准的来源加载代码,好比从网络来源,那就须要本身实现一个类加载器,从指定源进行加载。
主要是为了保证加载进来的字节流符合虚拟机规范,不会形成安全错误。
包括对于文件格式的验证,好比常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其余信息?
对于元数据的验证,好比该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
对于字节码的验证,保证程序语义的合理性,好比要保证类型转换的合理性。
对于符号引用的验证,好比校验符号引用中经过全限定名是否可以找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
主要是为类变量(注意,不是实例变量)分配内存,而且赋予初值。
特别须要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不一样变量类型的默认初始值。
好比8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final
static tmp = 456, 那么该阶段tmp的初值就是456。
将常量池内的符号引用替换为直接引用的过程。
两个重点:
符号引用:即一个字符串,可是这个字符串给出了一些可以惟一性识别一个方法,一个变量,一个类的相关信息。
直接引用:能够理解为一个内存地址,或者一个偏移量。好比类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量。
举个例子来讲,如今调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把全部的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
若是初始化一个类的时候,其父类还没有初始化,则优先初始化其父类。
若是同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
类加载过程只是一个类生命周期的一部分,在其前,有编译的过程,只有对源代码编译以后,才能得到可以被虚拟机加载的字节码文件;在其后还有具体的类使用过程,当使用完成以后,还会在方法区垃圾回收的过程当中进行卸载。若是想要了解Java类整个生命周期的话,能够自行上网查阅相关资料,这里再也不多作赘述。
答:
理论上Java由于有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被普遍使用于服务器端编程的一个重要缘由);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收也会发生内存泄露。
一个例子就是Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象。
下面的例子也展现了Java中发生内存泄露的状况:
package com.yuan_more;
import java.util.Arrays;
import java.util.EmptyStackException;
public class MyStack<T> {
private T[] elements;
private int size = 0;
private static final int INIT_CAPACITY = 16;
public MyStack(){
elements = (T[]) new Object[INIT_CAPACITY];
}
public void push(T elem){
ensureCapacity();
}
public T pop(){
if(size == 0){
throw new EmptyStackException();
}
return elements[-- size];
}
private void ensureCapacity() {
if(elements.length == size){
elements = Arrays.copyOf(elements,2 * size +1);
}
}
}
上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下彷佛没有什么明显的问题,它甚至能够经过你编写的各类单元测试。
然而其中的pop方法却存在内存泄露的问题,当咱们用pop方法弹出栈中的对象时,该对象不会被看成垃圾回收,即便使用栈的程序再也不引用这些对象,由于栈内部维护着对这些对象的过时引用(obsolete reference)。
在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无心识的对象保持。
若是一个对象引用被无心识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其余对象,即便这样的对象只有少数几个,也可能会致使不少的对象被排除在垃圾回收以外,从而对性能形成重大影响,极端状况下会引起Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至形成OutOfMemoryError。
答:
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会致使程序或系统的不稳定甚至崩溃。
Java提供的 GC 功能能够自动监测对象是否超过做用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操做方法。Java程序员不用担忧内存管理,由于垃圾收集器会自动进行管理。
要请求垃圾收集,能够调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,注意,只是请求,JVM什么时候进行垃圾回收具备不可预知性。
垃圾回收能够有效的防止内存泄露,有效的使用可使用的内存。垃圾回收器一般是做为一个单独的低优先级的线程运行,不可预知的状况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或全部对象进行垃圾回收。
在Java诞生初期,垃圾回收是Java最大的亮点之一,由于服务器端的编程须要有效的防止内存泄露问题,然而时过境迁,现在Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户一般以为iOS的系统比Android系统有更好的用户体验,其中一个深层次的缘由就在于Android系统中垃圾回收的不可预知性。
答:
由于有的对象寿命长,有的对象寿命短。应该将寿命长的对象放在一个区,寿命短的对象放在一个区。不一样的区采用不一样的垃圾收集算法。寿命短的区清理频次高一点,寿命长的区清理频次低一点,提升效率。
所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。
数据会首先分配到Eden区当中,固然也有特殊状况,若是是大对象那么会直接放入到老年代(大对象是指须要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC。新生代垃圾回收采用的是复制算法。
若是对象通过一次Minor GC还存活,而且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到必定的程度(默认为15)时,就会被晋升到老年代中了,固然晋升老年代的年龄是能够设置的。若是老年代满了就执行:Full GC, 由于不常常执行,所以老年代垃圾回收采用了标记-整理(Mark-Compact)算法。