一、JVM内存结构
虚拟机栈:存放基本数据类型、对象引用、方法出口等;
本地方法栈:服务于本地方法,线程私有
堆:java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享;
方法区:存放已被加载的类信息、常量、静态变量、既编译器编译后的代码数据等;
程序计数器:当前线程所执行字节码的行号指示器,用于记录正在执行的虚拟机字节码指令地址上,线程私有;
二、新生代、老年代模型
默认新生代与老年代比例为 1:2,可以通过-XX:NewRatio配置;
默认Edem:S0:S1 = 8:1:1,可以通过参数-XX:SurvivorRatio配置;
Survivor区中的对象被复制15次,可以晋升,对应虚拟机参数-XX:+MaxTenuringThreshold;
三、为什么要分Eden和Survivor?为什么要设置两个Survivor区?
如果没有Survivor区,Eden区每进行一次MinorGC,存活的对象就会被送到老年代。
老年代满了后,会触发MajorGC,老年代的内存空间远大于新生代,进行一次FullGC消耗的时间比MinorGC长的多,所以需要分为Eden和Suevivor区。
设置两个Survivor区,最大的好处是解决了内存碎片化,采用复制清除算法,保证了清除后内存是连续的,避免了碎片化的发生。
四、JVM中一次完整的GC流程是怎么样的,如何晋升到老年代?
- 对象先存储在Eden区,当Eden区满了后,Java虚拟机会触发一次MinorGC,以收集新生代的垃圾,存活下来的对象,则会转移到Survivor区。
- 大对象(需要大量连续内存空间的对象)直接进入老年代。
- 如果对象在Eden出生,并且经过第一次MinorGC后仍存活,并且被Survivor容纳的话,年龄设为1,每熬过一次MinorGC,年龄+1,年龄超过一定限制(默认15),则被晋升到老年态,即长期存活的对象进入到老年代。
- 老年代满了后而无法容纳更多的对象,MinorGC之后通常会进行FullGC,FullGC清理整个内存堆,包括年轻代和年老代。
- MajorGC发生在老年代的GC,清理老年代,经常会伴随至少一次MinjorGC,比MinorGC慢10倍以上。
五、有哪几种垃圾回收器?各自的优缺点?
- Serial New收集器:单线程收集器,收集垃圾时,会停止其他任务的执行,复制算法;
- Parallel New收集器:多线程收集器,复制算法;
- Servial Old收集器:单线程收集器,标记整理算法;
- Parallel Old收集器:多线程收集器,标记整理算法;
- CMS收集器:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法。运作过程:初始标记 、并发标记、重新标记、并发清除,收集结束会产生大量内存碎片;
- G1收集器:标记整理算法实现,运作流程如下:初始标记、并发标记、最终标记、筛选标记,不会产生内存碎片,可以精确地控制停顿;
六、CMS收集器和G1收集器的区别?
- CMS收集器是老年代的收集器,可以配合新生代的Serival和Parallal收集器一起使用;
- G1收集器收集范围是新生代和老年代,不需要结合其他收集器使用;
- CMS收集器以最小停顿时间为目标的收集器;
- G1收集器可预测垃圾回收的停顿时间;
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片;
- G1收集器是使用“标记-整理”算法,不会产生内存碎片。
七、JVM内存模型
- Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主内存直接进行数据同步进行。
八、volatile关键字
- 使用volatile关键字,保证了变量的可见性,防止指令重排,打破了内存屏障,当线程在使用变量的时候,都会从主内存中取新的变量值。
九、类加载器
BootStrap ClassLoader:加载<JAVA_HOME>\lib 目录下的类
Ext ClassLoader:加载<JAVA_HOME>\lib\ext 目录下的类
Application ClassLoader:加载classpath上的指定类库,默认使用这个类加载器
十、双亲委派模型
- 如果一个类加载器收到类加载的请求,它首先不会自己尝试加载这个类,而是把这个类委派给父类加载器加载。每个类加载器都是如此,只有当父类加载器在自己的搜索范围内找不到指定的类时,子加载器才尝试自己去加载。
十一、为什么要双亲委派机制?
- 如果没有双亲委派,用户自己定义一个java.lang.Object同名类,并把它放在ClassPath中,那么类之间的比较结果以及唯一性将无法保证。当有双亲委派机制后,可以防止内存中出现多份同样的字节码。
十二、如何打破双亲委派模型?
- 打破双亲委派机制不仅要继承ClassLoad类,还需要重写loadClass方法和findClass方法。
十三、JVM调优工具
- Jconsole:用于堆JVM中的内存、线程和类等进行监控。
- Jstatic:用于查看线程信息,例如查看死锁问题。
- Jmap:查看堆内存使用情况,导出Java进程的内存快照文件,一般采用MAT工具分析文件。
十四、JVM调优目标
- 减少全局变量和大对象。
- 调整新生代的大小。
- 设置老年代的大小。
- 选择合适的GC收集器。
十五、JVM常用参数
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewRatio 设置新生代和老年代的比例
- -XX:SurviorRatio Eden和S0、S1的比值
- -XX:+UseG1GC 使用G1收集器
十六、BTrace,Java线上问题排查神器
解决了什么问题?
- 哪些方法慢,例如监控执行时间超过1S的方法
- 查看哪些方法调用了System.gc()
- 查看方法参数或对象属性
- 执行某个方法抛出异常时,分析运行时参数
使用限制:
- 不能创建对象
- 不能使用数组
- 不能抛出或捕获异常
- 不能使用循环
- 不能使用synchronized关键字
- 属性和方法必须使用static修饰
十七、类加载过程
加载->验证->准备->解析->初始化
十八、CMS运行原理
- 初始标记:会stop the world停止工作线程,速度快。
- 并发标记:系统一边工作,一边进行垃圾回收,工作期间会陆续有对象进入老年代,很耗时。
- 并发清理:和系统并发运行,不影响工作。清理掉被标记的垃圾对象,并移动整理。
十九、G1运行原理
- 初始标记
- 并发标记
- 最终标记
- 清楚标记
二十、强软弱虚使用场景
- 强引用:正常的创建对象,只要引用存在,永远不会被GC回收,即使OOM。
- 软引用:内存溢出之前进行回收,GC时内存不足时回收,如果内存足够就不回收。
- 弱引用:每次GC时进行回收,无论内存是否足够。