总结了JVM一些经典面试题,分享出我本身的解题思路,但愿对你们有帮助,有哪里你以为不正确的话,欢迎指出,后续有空会更新。html
思路: 描述栈定义,再描述为何会溢出,再说明一下相关配置参数,OK的话能够给面试官手写是一个栈溢出的demo。java
个人答案:面试
思路: 给面试官画一下JVM内存模型图,并描述每一个模块的定义,做用,以及可能会存在的问题,如栈溢出等。算法
个人答案:数组
程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。浏览器
Java虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。缓存
Native方法栈:和虚拟栈类似,只不过它服务于Native方法,线程私有。bash
Java堆:java内存最大的一块,全部对象实例、数组都存放在java堆,GC回收的地方,线程共享。多线程
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),回收目标主要是常量池的回收和类型的卸载,各线程共享并发
思路: 先讲一下JAVA堆,新生代的划分,再谈谈它们之间的转化,相互之间一些参数的配置(如: –XX:NewRatio,–XX:SurvivorRatio等),再解释为何要这样划分,最好加一点本身的理解。
个人答案:
共享内存区 = 持久带 + 堆
持久带 = 方法区 + 其余
Java堆 = 老年代 + 新生代
新生代 = Eden + S0 + S1
思路: 先描述一下Java堆内存划分,再解释Minor GC,Major GC,full GC,描述它们之间转化流程。
个人答案:
思路: 必定要记住典型的垃圾收集器,尤为cms和G1,它们的原理与区别,涉及的垃圾回收算法。
个人答案:
思路: 先画出Java内存模型图,结合例子volatile ,说明什么是重排序,内存屏障,最好能给面试官写如下demo说明。
个人答案:
Java内存模型规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存,线程的工做内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存。不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量的传递均须要本身的工做内存和主存之间进行数据同步进行。
在这里,先看一段代码
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指令,用于控制特定条件下的重排序和内存可见性问题。
思路: 先说明一下什么是类加载器,能够给面试官画个图,再说一下类加载器存在的意义,说一下双亲委派模型,最后阐述怎么打破双亲委派模型。
个人答案:
类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。
- 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
- 其余类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的全部类库。
- 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,咱们能够直接使用这个类加载器。通常状况,若是咱们没有自定义类加载器默认就是用这个加载器。
双亲委派模型工做过程是:
若是一个类加载器收到类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器完成。每一个类加载器都是如此,只有当父加载器在本身的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试本身去加载。
双亲委派模型图:
在这里,先想一下,若是没有双亲委派,那么用户是否是能够本身定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的惟一性将没法保证,所以,为何须要双亲委派模型?防止内存中出现多份一样的字节码
打破双亲委派机制则不只要继承ClassLoader类,还要重写loadClass和findClass方法。
思路: 能够说一下堆栈配置相关的,垃圾收集器相关的,还有一下辅助信息相关的。
个人答案:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
复制代码
-Xmx3550m: 最大堆大小为3550m。
-Xms3550m: 设置初始堆大小为3550m。
-Xmn2g: 设置年轻代大小为2g。
-Xss128k: 每一个线程的堆栈大小为128k。
-XX:MaxPermSize: 设置持久代大小为16m
-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。
-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入年老代。
-XX:+UseParallelGC
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC
-XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection:
复制代码
-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。
-XX:ParallelGCThreads=20: 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction:因为并发收集器不对内存空间进行压缩、整理,因此运行一段时间之后会产生“碎片”,使得运行效率下降。此值设置运行多少次GC之后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,可是能够消除碎片
-XX:+PrintGC
-XX:+PrintGCDetails
复制代码
-XX:+PrintGC 输出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails 输出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs
思路: 能够说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。
个人答案:
思路: 先说一下四种引用的定义,能够结合代码讲一下,也能够扩展谈到ThreadLocalMap里弱引用用处。
个人答案:
咱们平时new了一个对象就是强引用,例如 Object obj = new Object();即便在内存不足的状况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
若是一个对象只具备软引用,则内存空间足够,垃圾回收器就不会回收它;若是内存空间不足了,就会回收这些对象的内存。
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); // 从新构建
}
复制代码
具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str=null;
等价于
str = null;
System.gc();
复制代码
若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
欢迎你们关注,你们一块儿学习,一块儿讨论。