[toc]
前段时间在忙大数据,也对市场技术有了一个新的了解,学到老,学到老嘛。明天来和大家分享一下JVM常见的面试题,明天来和大家分享一下。大多都是大厂的实战面试题,来和小刘看一下吧!
1、JVN内存构造
办法区和对是所有线程共享的内存区域;而java栈、本地办法栈和程序员计数器是运行是线程公有的内存区域。
- Java堆(Heap),是Java虚拟机所治理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,简直所有的对象实例都在这里分配内存。
- 办法区(Method Area),办法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、动态变量、即时编译器编译后的代码等数据。
- 程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用能够看做是以后线程所执行的字节码的行号指示器。
- JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程公有的,它的生命周期与线程雷同。虚拟机栈形容的是Java办法执行的内存模型:每个办法被执行的时候都会同时创立一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动静链接、办法进口等信息。每一个办法被调用直至执行实现的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
- 本地办法栈(Native Method Stacks),本地办法栈(Native Method Stacks)与虚拟机栈所施展的作用是十分类似的,其区别不过是虚拟机栈为虚拟机执行Java办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的Native办法服务。
2、对象调配规定
- 对象优先调配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
- 大对象间接进入老年代(大对象是指须要大量间断内存空间的对象)。这样做的目标是防止在Eden区和两个Survivor区之间产生大量的内存拷贝(新生代采纳复制算法收集内存)。
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象通过了1次Minor GC那么对象会进入Survivor区,之后每通过一次Minor GC那么对象的年龄加1,晓得达到阀值对象进入老年区。
- 动静判断对象的年龄。如果Survivor区中雷同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象能够间接进入老年代。
- 空间调配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的均匀大小,如果这个值大于老年区的残余值大小则进行一次Full GC,如果小于查看HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
3、解释内存中的栈(stack)、堆(heap)和动态区(static area)的用法
通常咱们定义一个根本数据类型的变量,一个对象的援用,还有就是函数调用的现场保留都应用内存中的栈空间;而通过new关键字和结构器创立的对象放在堆空间;程序中的字面量(literal)如间接书写的100、"hello"和常量都是放在动态区中。栈空间操作起来最快然而栈很小,通常大量的对象都是放在堆空间,实践上整个内存没有被其余过程应用的空间甚至硬盘上的虚拟内存都能够被当成堆空间来应用。
String str = new String("hello");
下面的语句中变量str放在栈上,用new创立进去的字符串对象放在堆上,而"hello"这个字面量放在动态区。
4、Perm Space中保留什么数据?会引起OutOfMemory吗?
Perm Space中保留的是加载class文件。
会引起OutOfMemory,出现异常能够设置 -XX:PermSize 的大小。JDK 1.8后,字符串常量不寄存在永恒带,而是在堆内存中,JDK8当前没有永恒代概念,而是用元空间代替,元空间不存在虚拟机中,二是应用本地内存。
5、什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的办法区内,而后在堆区创立一个java.lang.Class对象,用来封装类在办法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在办法区内的数据结构,并且向Java程序员提供了拜访办法区内的数据结构的接口。
类加载器
- 启动类加载器:Bootstrap ClassLoader,负责加载寄存在JDK\jre\lib(JDK代表JDK的装置目录,下同)下,或被-Xbootclasspath参数指定的门路中的,并且能被虚拟机辨认的类库
- 扩大类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs零碎变量指定的门路中的所有类库(如javax.*结尾的类),开发者能够间接应用扩大类加载器。
- 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类门路(ClassPath)所指定的类,开发者能够间接应用该类加载器
双亲委派机制:类加载器收到类加载申请,本人不加载,向上委托给父类加载,父类加载不了,再本人加载。劣势就是防止Java外围API篡改。
6、如何⾃定义⼀个类加载器?你使⽤过哪些或者你在什么场景下须要⼀个⾃ 定义的类加载器吗?
自定义类加载的意义:
- 加载特定门路的class文件
- 加载一个加密的网络class文件
- 热部署加载class文件
7、形容一下JVM加载class文件的原理机制?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时零碎组件,它负责在运行时查找和装入类文件中的类。
因为Java的跨平台性,通过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序须要应用某个类时,JVM会确保这个类曾经被加载、连贯(验证、筹备和解析)和初始化。
类的加载是指把类的.class文件中的数据读入到内存中,通常是创立一个字节数组读入.class文件,而后产生与所加载类对应的Class对象。加载实现后,Class对象还不残缺,所以此时的类还不可用。当类被加载后就进入连贯阶段,这一阶段包含验证、筹备(为动态变量分配内存并设置默认的初始值)和解析(将符号援用替换为间接援用)三个步骤。最初JVM对类进行初始化,包含:1)如果类存在间接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就顺次执行这些初始化语句。类的加载是由类加载器实现的,类加载器包含:根加载器(BootStrap)、扩大加载器(Extension)、零碎加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保障了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其余的加载器都有且仅有一个父类加载器。类的加载首先申请父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的援用。
上面是对于几个类加载器的阐明:
*
- bootstrap:个别用本地代码实现,负责加载JVM根底外围类库(rt.jar);
- Extension:从java.ext.dirs零碎属性所指定的目录中加载类库,它的父加载器是Bootstrap;
- System:又叫利用类加载器,其父类是Extension。它是利用最宽泛的类加载器。它从环境变量classpath或者零碎属性java.class.path所指定的目录中记录类,是用户自定义加载器的默认父加载器。
8、Java对象创立过程
- JVM遇到一条新建对象的指令时首先去查看这个指令的参数是否能在常量池中定义到一个类的符号援用。而后加载这个类(类加载过程在后边讲)
- 为对象分配内存。一种方法"指针碰撞"、一种方法"闲暇列表",最终罕用的方法"本地线程缓冲调配(TLAB)"
- 将除对象头外的对象内存空间初始化为0
- 对对象头进行必要设置
9、类的生命周期
类的生命周期包含这几个局部,加载、连贯、初始化、应用和卸载,其中前三部是类的加载的过程,如下图:
- 加载,查找并加载类的二进制数据,在Java堆中也创立一个java.lang.Class类的对象
- 连贯,连贯又蕴含三块内容:验证、筹备、初始化。 1)验证,文件格式、元数据、字节码、符号援用验证; 2)筹备,为类的动态变量分配内存,并将其初始化为默认值; 3)解析,把类中的符号援用转换为间接援用
- 初始化,为类的动态变量赋予正确的初始值
- 应用,new出对象程序中应用
- 卸载,执行垃圾回收
10、Java 中会存在内存透露吗,请简略形容。
实践上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被宽泛应用于服务器端编程的一个重要起因);然而在理论开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因而也会导致内存泄露的产生。例如hibernate的Session(一级缓存)中的对象属于长久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时敞开(close)或清空(flush)一级缓存就可能导致内存泄露。上面例子中的代码也会导致内存泄露。
import java.util.Arrays;import java.util.EmptyStackException;public class MyStack { 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(); elements[size++] = elem; } 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。
11、GC是什么?为什么要有GC?
GC是垃圾收集的意思,内存解决是编程人员容易呈现问题的中央,遗记或者谬误的内存回收会导致程序或零碎的不稳固甚至解体,Java提供的GC性能能够主动监测对象是否超过作用域从而达到主动回收内存的目标,Java语言没有提供开释已分配内存的显示操作方法。 Java程序员不必放心内存治理,因为垃圾收集器会主动进行治理。要申请垃圾收集,能够调用上面的办法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能够屏蔽掉显示的垃圾回收调用。 垃圾回收能够无效的避免内存泄露,无效的应用能够应用的内存。垃圾回收器通常是作为一个独自的低优先级的线程运行,不可预知的状况下对内存堆中曾经死亡的或者长时间没有应用的对象进行革除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。 在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程须要无效的避免内存泄露问题,然而时过境迁,现在Java的垃圾回收机制曾经成为被诟病的货色。挪动智能终端用户通常感觉iOS的零碎比Android零碎有更好的用户体验,其中一个深层次的起因就在于Android零碎中垃圾回收的不可预知性。
补充:垃圾回收机制有很多种,包含:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等形式。规范的Java过程既有栈又有堆。栈保留了原始型局部变量,堆保留了要创立的对象。Java平台对堆内存回收和再利用的根本算法被称为标记和革除,然而Java对其进行了改良,采纳"分代式垃圾收集"。这种办法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象挪动到不同区域:
- 伊甸园(Eden):这是对象最后诞生的区域,并且对大多数对象来说,这里是它们惟一存在过的区域。
- 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
- 一生颐养园(Tenured):这是足够老的幸存对象的归宿。年老代收集(Minor-GC)过程是不会涉及这个中央的。当年老代收集不能把对象放进一生颐养园时,就会触发一次齐全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
与垃圾回收相干的JVM参数:
- -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
- -Xmn — 堆中年老代的大小
- -XX:-DisableExplicitGC — 让System.gc()不产生任何作用
- -XX:+PrintGCDetails — 打印GC的细节
- -XX:+PrintGCDateStamps — 打印GC操作的工夫戳
- -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小
- -XX:NewRatio — 能够设置老生代和新生代的比例
- -XX:PrintTenuringDistribution — 设置每次新生代GC后输入幸存者乐园中对象年龄的散布
- -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
- -XX:TargetSurvivorRatio:设置幸存区的指标使用率
12、做GC时,⼀个对象在内存各个Space中被挪动的程序是什么?
标记革除法,复制算法,标记整顿、分代算法。
新生代个别采纳复制算法 GC,老年代应用标记整顿算法。
垃圾收集器:串行新生代收集器、串行老生代收集器、并行新生代收集器、并行老年代收集器。
CMS(Current Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器,它是一种并发收集器,采纳的是Mark-Sweep算法。
13、你晓得哪些垃圾回收算法?
GC最根底的算法有三种: 标记 -革除算法、复制算法、标记-压缩算法,咱们罕用的垃圾回收器个别都采纳分代收集算法。
- 标记-革除算法,"标记-革除"(Mark-Sweep)算法,如它的名字一样,算法分为"标记"和"革除"两个阶段:首先标记出所有须要回收的对象,在标记实现后对立回收掉所有被标记的对象。
- 复制算法,"复制"(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只应用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块下面,而后再把已应用过的内存空间一次清理掉。
- 标记-压缩算法,标记过程依然与"标记-革除"算法一样,但后续步骤不是间接对可回收对象进行清理,而是让所有存活的对象都向一端挪动,而后间接清理掉端边界以外的内存
- 分代收集算法,"分代收集"(Generational Collection)算法,把Java堆分为新生代和老年代,这样就能够依据各个年代的特点采纳最适当的收集算法。
14、垃圾回收器
- Serial收集器,串行收集器是最古老,最稳固以及效率高的收集器,可能会产生较长的进展,只应用一个线程去回收。
- ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
- Parallel收集器,Parallel Scavenge收集器相似ParNew收集器,Parallel收集器更关注零碎的吞吐量。
- Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,应用多线程和"标记-整顿"算法
- CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器。
- G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,次要针对装备多颗处理器及大容量内存的机器. 以极高概率满足GC进展工夫要求的同时,还具备高吞吐量性能特色
15、如何判断一个对象是否应该被回收
判断对象是否存活个别有两种形式:
- 援用计数:每个对象有一个援用计数属性,新增一个援用时计数加1,援用开释时计数减1,计数为0时能够回收。此办法简略,无奈解决对象互相循环援用的问题。
- 可达性剖析(Reachability Analysis):从GC Roots开始向下搜寻,搜寻所走过的门路称为援用链。当一个对象到GC Roots没有任何援用链相连时,则证实此对象是不可用的,不可达对象。
16、JVM的永恒代中会产生垃圾回收么?
垃圾回收不会产生在永恒代,如果永恒代满了或者是超过了临界值,会触发齐全垃圾回收(Full GC)。如果你认真查看垃圾收集器的输入信息,就会发现永恒代也是被回收的。这就是为什么正确的永恒代大小对防止Full GC是十分重要的起因。请参考下Java8:从永恒代到元数据区 (注:Java8中曾经移除了永恒代,新加了一个叫做元数据区的native内存区)
17、援用的分类
- 强援用:GC时不会被回收
- 软援用:形容有用但不是必须的对象,在产生内存溢出异样之前被回收
- 弱援用:形容有用但不是必须的对象,在下一次GC时被回收
- 虚援用(幽灵援用/幻影援用):无奈通过虚援用取得对象,用PhantomReference实现虚援用,虚援用用来在GC时返回一个告诉。
18、调优命令
Sun JDK监控和故障解决命令有jps jstat jmap jhat jstack jinfo
- jps,JVM Process Status Tool,显示指定零碎内所有的HotSpot虚拟机过程。
- jstat,JVM statistics Monitoring是用于监督虚拟机运行时状态信息的命令,它能够显示出虚拟机过程中的类装载、内存、垃圾收集、JIT编译等运行数据。
- jmap,JVM Memory Map命令用于生成heap dump文件
- jhat,JVM Heap Analysis Tool命令是与jmap搭配应用,用来剖析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的剖析后果后,能够在浏览器中查看
- jstack,用于生成java虚拟机以后时刻的线程快照。
- jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。
19、调优工具
罕用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
- jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和治理控制台,用于对JVM中内存,线程和类等的监控
- jvisualvm,jdk自带全能工具,能够剖析内存快照、线程快照;监控内存变动、GC变动等。
- MAT,Memory Analyzer Tool,一个基于Eclipse的内存剖析工具,是一个疾速、功能丰富的Java heap剖析工具,它能够帮忙咱们查找内存透露和缩小内存耗费
- GChisto,一款业余剖析gc日志的工具
20、jstack 是⼲什么的? jstat 呢?如果线上程序周期性地呈现卡顿,你狐疑可 能是 GC 导致的,你会怎么来排查这个问题?线程⽇志⼀般你会看其中的什么 局部?
jstack 用来查问 Java 过程的堆栈信息。
jvisualvm 监控内存泄露,跟踪垃圾回收、执行时内存、cpu剖析、线程剖析。
21、Minor GC与Full GC别离在什么时候产生?
新生代内存不够用时候产生MGC也叫YGC,JVM内存不够的时候产生FGC
22、你有没有遇到过OutOfMemory问题?你是怎么来解决这个问题的?解决 过程中有哪些播种?
permgen space、heap space 谬误。
常见的起因
*
- 内存加载的数据量太大:一次性从数据库取太多数据;
- 汇合类中有对对象的援用,应用后未清空,GC不能进行回收;
- 代码中存在循环产生过多的反复对象;
- 启动参数堆内存值小。
23、JDK 1.8之后Perm Space有哪些变动? MetaSpace⼤⼩默认是⽆限的么? 还是你们会通过什么⽅式来指定⼤⼩?
JDK 1.8后用元空间代替了 Perm Space;字符串常量寄存到堆内存中。
MetaSpace大小默认没有限度,个别依据零碎内存的大小。JVM会动静扭转此值。
-XX:MetaspaceSize:调配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark)。此值为估计值,MetaspaceSize的值设置的过大会缩短垃圾回收工夫。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。
-XX:MaxMetaspaceSize:调配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限度,但应取决于零碎内存的大小。JVM会动静地扭转此值。
24、StackOverflow异样有没有遇到过?⼀般你猜想会在什么状况下被触发?如何指定⼀个线程的堆栈⼤⼩?⼀般你们写多少?
栈内存溢出,个别由栈内存的局部变量过爆了,导致内存溢出。呈现在递归办法,参数个数过多,递归过深,递归没有进口。
25. Java四援用
- 强援用(StrongReference)强援用是应用最广泛的援用。如果一个对象具备强援用,那垃圾回收器绝不会回收它。当内存空间有余,Java虚拟机宁愿抛出OutOfMemoryError谬误,使程序异样终止,也不会靠随便回收具备强援用的对象来解决内存不足的问题
- 软援用(SoftReference)
如果内存空间有余了,就会回收这些对象的内存。只有垃圾回收器没有回收它,软援用能够和一个援用队列(ReferenceQueue)联结应用,如果软援用所援用的对象被垃圾回收器回收,Java虚拟机就会把这个软援用退出到与之关联的援用队列中 - 弱援用(WeakReference)
弱援用与软援用的区别在于:只具备弱援用的对象领有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具备弱援用的对象,不论以后内存空间足够与否,都会回收它的内存。
弱援用能够和一个援用队列(ReferenceQueue)联结应用,如果弱援用所援用的对象被垃圾回收,Java虚拟机就会把这个弱援用退出到与之关联的援用队列中 - 虚援用(PhantomReference)
虚援用在任何时候都可能被垃圾回收器回收,次要用来跟踪对象被垃圾回收器回收的流动,被回收时会收到一个零碎告诉。虚援用与软援用和弱援用的一个区别在于:虚援用必须和援用队列 (ReferenceQueue)联结应用。当垃圾回收器筹备回收一个对象时,如果发现它还有虚援用,就会在回收对象的内存之前,把这个虚援用退出到与之关联的援用队列中。
26. GC 标记对象的死活
- 援用计数法:给对象增加一个援用计数器,没当被援用的时候,计数器的值就加一。援用生效的时候减一,当计数器的值为 0 的时候就示意改对象能够被 GC 回收了,弊病:A->B,B->A,那么 AB 将永远不会被回收了。也就是援用有环的状况
根搜索算法(可达性算法) GC Roots Tracing:通过一个叫 GC Roots 的对象作为终点,从这些结点开始向下搜寻,搜寻所走过的门路称为援用链,当一个对象没有与任何的援用链相连的时候则改对象就能够被。 GC 回收回收了Roots 包含:java 虚拟机栈中援用的对象,本地办法栈中援用的对象,办法区中常量援用的对象,办法区中动态属性援用的对象
- 在Java语言里,可作为GC Roots的对象包含以下几种:
虚拟机栈(栈帧中的本地变量表)中的援用的对象 办法区中的类动态属性援用的对象 办法区中的常量援用的对象。 本地办法栈中JNI(即个别说的Native办法)的援用的对象。
27. 引起类加载操作的五个行为
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令
- 反射调用的时候,如果类没有进行过初始化,则须要先触发其初始化
- 子类初始化的时候,如果其父类还没初始化,则需先触发其父类的初始化
- 虚拟机执行主类的时候(有 main(string[] args))
- JDK1.7 动静语言反对
28. Java对象创立机会
- 应用new关键字创建对象
- 应用Class类的newInstance办法(反射机制)
- 应用Constructor类的newInstance办法(反射机制)
- 应用Clone办法创建对象
- 应用(反)序列化机制创建对象
有人喜爱说,就这 就这 ,其实当你晓得了下面 这些面试题之后,即便下面没有提的 ,你也能够灵便应用了 逻辑的形式去答复,瞎扯,面试官,也会感觉 你 np
本文由博客一文多发平台 OpenWrite 公布!