面试专题(JVM 虚拟机)

Java内存模型问题

jvm 进程内存逻辑结构

直接内存:不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;
 如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;
 这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

你对jvm内存结构了解吗?

程序计数器:较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响;

java 栈:线程私有,生命周期和线程,每个方法在执行的同时都会创建一个 栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型和对象的引用;

本地方法栈:本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法;

堆:Java堆是Javaer需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等);

方法区:也叫永久区,用于存储已经被虚拟机加载的类信息,常量("zdy","123"等),静态变量(static变量)等数据。

运行时常量池:运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量("zdy","123"等)和符号引用。

你对jvm内存结构了解吗?线程共享与线程私有

堆和栈的区别是什么?

  功能
 以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;
 而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;


 线程独享还是共享
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

 空间大小
的内存要远远小于堆内存,栈的深度是有限制的,如果递归没有及时跳出,很可能发生StackOverFlowError问题。
 你可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值

你对jvm内存结构了解吗?堆和栈

你对jvm内存结构了解吗?线程安全的本质

jdk1.6、jdk1.7和jdk1.8内存结构区别

jdk1.8的jvm 进程内存逻辑结构

为什么去除方法区

 永久代来存储类信息、常量、静态变量等数据不是个好主意, 很容易遇到内存溢出的问题.JDK8的实现中将类的元数据放入 native memory, 将字符串池和类的静态变量放入java堆中. 可以使用MaxMetaspaceSize对元数据区大小进行调整;
 对永久代进行调优是很困难的,同时将元空间与堆的垃圾回收进行了隔离,避免永久代引发的Full GC和OOM等问题;

jvm常用内存参数设置

注意: java8去掉了-XX:PermSize和-XX:MaxPermSize,新增了-XX:MetaspaceSize和-XX:MaxMetaspaceSize

常见内存溢出异常问题

有哪些java内存溢出异常?

java内存溢出异常主要有两个:
 OutOfMemeoryError:当堆、栈(多线程情况)、方法区、元数据区、直接内存中数据达到最大容量时产生;
 StackOverFlowError:如果线程请求的栈深度大于虚拟机锁允许的最大深度,将抛出StackOverFlowError,其本质还是数据达到最大容量;

什么情况下出现堆溢出?怎么解决?

  产生原因
堆用于存储实例对象,只要不断创建对象,并且保证GC Roots到对象之间有引用的可达,避免垃圾收集器回收实例对象,就会在对象数量达到堆最大容量时产生OutOfMemoryError异常。
java.lang.OutOfMemoryError: Java heap space

  解决办法
使用-XX:+HeapDumpOnOutOfMemoryError可以让java虚拟机在出现内存溢出时产生当前堆内存快照以便进行异常分析,主要分析那些对象占用了内存;也可使用jmap将内存快照导出;一般检查哪些对象占用空间比较大,由此判断代码问题,没有问题的考虑调整堆参数;

什么情况下出现栈溢出?怎么解决?

  产生原因
 如果线程请求的栈深度大于虚拟机锁允许的最大深度,将抛出StackOverFlowError;
 如果虚拟机在扩展栈时无法申请到足够的内存空间,抛出OutOfMemeoryError;

  解决办法
 StackOverFlowError 一般是函数调用层级过多导致,比如死递归、死循环;
 OutOfMemeoryError一般是在多线程环境才会产生,一般用“减少内存的方法”,既减少最大堆和减少栈容量来换取更多的线程支持;

什么情况下出现方法区或元数据区溢出?怎么解决?

  产生原因
 jdk 1.6以前,运行时常量池还是方法区一部分,当常量池满了以后(主要是字符串变量),会抛出OOM异常;
 方法区和元数据区还会用于存放class的相关信息,如:类名、访问修饰符、常量池、方法、静态变量等;当工程中类比较多,而方法区或者元数据区太小,在启动的时候,也容易抛出OOM异常

  解决办法
 jdk 1.7之前,通过-XX:PermSize,-XX:MaxPerSize,调整方法区的大小;
 jdk 1.8以后,通过-XX:MetaspaceSize ,-XX:MaxMetaspaceSize,调整元数据区的大小;

什么情况下出现本机直接内存溢出?怎么解决?

  产生原因
jdk本身很少操作直接内存,而直接内存(DirectMemory)导致溢出最大的特征是,Heap Dump文件不会看到明显异常,而程序中直接或者间接的用到了NIO;
  解决办法
直接内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样)

垃圾回收面试问题

关于垃圾回收我们必须要了解的知识

 垃圾回收主要回收的是堆内存,基于分代的思想:

内存怎么样分配

  对象分配
 优先在Eden区分配。当Eden区没有足够空间分配时, VM发起一次Minor GC, 将Eden区和其中一块Survivor区内尚存活的对象放入另一块Survivor区域。如MinorGC时survivor空间不够,对象提前进入老年代,老年代空间不够时进行Full GC;
 大对象直接进入老年代,避免在Eden区和Survivor区之间产生大量的内存复制, 此外大对象容易导致还有不少空闲内存就提前触发GC以获取足够的连续空间.
  对象晋级
 年龄阈值:VM为每个对象定义了一个对象年龄(Age)计数器, 经第一次Minor GC后仍然存活, 被移动到Survivor空间中, 并将年龄设为1. 以后对象在Survivor区中每熬过一次Minor GC年龄就+1. 当增加到一定程度(-XX:MaxTenuringThreshold, 默认15), 将会晋升到老年代.
 提前晋升: 动态年龄判定;如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代, 而无须等到晋升年龄.

哪些要收回?对象生死判定

  可达性分析算法
通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的;

在Java, 可作为GC Roots的对象包括:
1.方法区: 类静态属性引用的对象;
2.方法区: 常量引用的对象;
3.虚拟机栈(本地变量表)中引用的对象.
4.本地方法栈JNI(Native方法)中引用的对象。

怎么回收?方法论?分代收集

  新生代- 标记清除法
该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象.

  缺点
 效率问题: 标记和清除过程的效率都不高;
 空间问题: 标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内
存而不得不提前触发另一次垃圾收集.

  新生代- 复制算法
该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象复制到另外一块上面, 然后把已使用过的内存空间一次清理掉.

  优点
 由于是每次都对整个半区进行内存回收,内存分配时不必考虑内存碎片问题。
 垃圾回收后空间连续,只要移动堆顶指针,按顺序分配内存即可;
 特别适合java朝生夕死的对象特点;


  缺点
 内存减少为原来的一半,太浪费了;
 对象存活率较高的时候就要执行较多的复制操作,效率变低;、
 如果不使用50%的对分策略,老年代需要考虑的空间担保策略

怎么回收?方法论?分代收集

  老年代- 标记整理算法
该算法分为“标记”和“清除”两个阶段:  首先标记出所有需要回收的对象(可达性分析), 在标记完成后让所有存活的对象都向一端移动,然后清理掉端边界以外的内存;

  优点
 不会损失50%的空间;
 垃圾回收后空间连续,只要移动堆顶指针,按顺序分配内存即可;
 比较适合有大量存活对象的垃圾回收;
  缺点
 标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

实现回收?谁来做?垃圾回收器

实现回收?谁来做?垃圾回收器

垃圾回收默认配置及互联网后台推荐配置

 在JVM的客户端模式(Client)下,JVM默认垃圾收集器是串行垃圾收集器(Serial GC + Serial Old,-XX:+USeSerialGC);
 在JVM服务器模式(Server)下默认垃圾收集器是并行垃圾收集器(ParallelScavaenge +Serial Old,-XX:+UseParallelGC)
 而适用于Server模式下
 ParNew + CMS + SerialOld(失败担保),-XX:UseConcMarkSweepGC;
 Parallel scavenge + Parallel,-XX:UseParallelOldGC

JVM 垃圾回收面试常见面试题

 JVM 垃圾回收面试常见面试题
 垃圾回收常用的算法有哪些?特点是什么?(见垃圾回收算法)
 哪几种垃圾收集器,各自的优缺点,重点讲下cms(见垃圾回收器)
 jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代等(见内存怎么样分配)
 JVM垃圾回收机制,何时触发MinorGC或FullGC等操作
答:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说;Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:
 System.gc()
 老年代空间不足
 永生区空间不足
 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
 堆中分配很大的对象

性能调优工具问题

面试题

1. 你常用的调优工具有哪些?
2. 如果碰到应用故障你怎么样排除问题?

java常用调优工具

堆dump分析

 堆dump分析:堆dump分析主要目的是定位OOM异常的原因;解决oom问题四部曲:
1. 分析OOM异常的原因,堆溢出?栈溢出?本地内存溢出?
2. 如果是堆溢出,导出堆dump,并对堆内存使用有个整体了解;
3. 找到最有可能导致内存泄露的元凶,通常也就是消耗内存最多的对象;
4. 使用辅助工具对dump文件进行分析;
 注意其他几类造成OOM异常的原因:
1. Direct Memory
2. 线程堆栈:
单线程:StackOverflowError
多线程:OutOfMemoryError:unable to create new native thread
3. Socket 缓冲区:IOException:Too many open files

线程dump分析

 线程dump分析:线程dump分析主要目的是定位线程长时间停顿的原因;

应用故障你怎么样排除问题?

 应用故障一般指应用运行缓慢、用户体验差或者周期性的出现卡顿,排除的思路:
1. 检查应用所在的生产环境的软硬件以及网络环境,排除外围因素;
2. 确定是否为OOM异常,这类异常影响最恶劣,但是比较容易排查;
3. 确定是否有大量长时间停顿的应用线程,非常占用cpu资源;
4. 周期性的卡顿很可能是垃圾回收造成,web后端系统建议使用cms垃圾回收器;

类加载机制问题

类的完整生命周期

什么时候出发类加载?

1. 使用new关键字实例化对象,读取或者设置一个类的静态变量的时候,调用类的静态方法的时候;
2. 对类进行反射调用的时候;
3. 初始化子类时,父类会先被初始化;
4. 对类使用动态代理的时候需要先被初始化

谈下你对双亲委派模型理解?

谈下你对双亲委派模型理解?

 双亲委派模型好处
Java类随着它的类加载器一起具备了带有优先级的层次关系,保证java程序稳定运行

tomcat 类加载机制

 同一个tomcat容器下的两个应用以及lib目录中都有UserServiceImpl类,tomcat怎么样保证类的隔离性?

类加载器与类的唯一性:类加载器虽然只用于实现类的加载动作,但是对于任意一个类,都需要由加载它的类加载器和这个类本身共同确立其在Java虚拟机中的唯一性。通俗的说,JVM中两个类是否“相等”,首先就必须是同一个类加载器加载的,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要类加载器不同,那么这两个类必定是不相等的。

 Tomcat目录结构中,有三组目录(“/common/*”,“/server/*”和“shared/*”)可以存放公用Java类库,此外还有第四组Web应用程序自身的目录“/WEB-INF/*”,把java类库放置在这些目录中的含义分别是:
 放置在common目录中:类库可被Tomcat和所有的Web应用程序共同使用。
 放置在server目录中:类库可被Tomcat使用,但对所有的Web应用程序都不可见。
 放置在shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
 放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。
 注意:tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托

猜你喜欢

转载自my.oschina.net/u/3728166/blog/2873179
今日推荐