1.介绍一下JVM的内存模型
- 线程私有的,有虚拟机栈,本地方法栈,程序计数器
- 线程共享的,有堆和方法区
程序计数器
看作是当前线程执行的字节码行号的一个指示器,指示下一个要执行的语句,所以必须是线程私有的
虚拟机栈
就是线程的栈,保存局部变量。当执行一个方法时,会向这个栈区放入一个栈帧
本地方法栈
和虚拟机栈服务,区别就是虚拟机栈是为Java字节码服务;而本地方法栈是为本地native方法服务
方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
堆区(重点)
- 堆区分为新生区和老年区,默认占比是
1:2
,其中新生代包括Eden区,S1和S2区 - 采用分代回收算法(即每一代所用的垃圾收集算法都不一样)
- 新生代使用复制算法+标记清除算法,新生代分为3个区,Eden区,S1,S2区,默认占比是
8:1:1
,当Eden区的空间用完时,JVM会发生minor GC
- 把Eden区+S1区存活的对象放到S2区
- 清空Eden区和S1区
- From区和To区交换(指指针指向)
- 每次From区和To区发生交换,对象的年龄+1,当年龄到达15,升级到老年区,对象过大的话也会直接创建到老年区
- 老年区空间占比达到某个值的时候,会出发全局垃圾回收Full GC,一般使用标记清除算法。如果Full GC仍然无法进行对象的保存,会产生OOM异常
- 新生代使用复制算法+标记清除算法,新生代分为3个区,Eden区,S1,S2区,默认占比是
2.GC的算法
- 引用计数器
- 复制算法,优点快速,没有碎片;缺点是空间利用率不高,需要双倍的空间(S1区和S2区的存在)
- 标记清除算法,有空间碎片,但是不需要额外空间
- GC ROOT遍历所有的GC ROOTs,把不可达的清除
- 标记整理算法,标记清除算法+整理
一定要清楚GC ROOT会暂停整个程序
3.Minor GC , Full GC的触发条件是什么?
-
Minor GC触发条件:
当Eden区满的时候,触发Minor GC
-
Full GC的触发条件:
System.gc()
方法的调用- 老年代空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
4.类加载的过程
加载—>连接—>初始化
连接又分为:验证,准备,解析
-
验证
确保加载的类信息符合JVM规范,没有安全方面的问题。 -
准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。 -
解析
虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程。 -
Java程序对类的使用方式可分为两种
- 主动使用
- 被动使用
-
所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”才初始化他们
5.怎么表示一个对象是可回收的?
基本思路就是通过一系列名为”GCRoots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GCRoots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,没有被遍历到的就自然被判定为死亡。
可以作为GC ROOT的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的native方法)中引用的对象
6.类加载器,可以打破双亲委派么?
1)什么是类加载器?
类加载器 就是根据指定全限定名称将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)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
2)双亲委派模型工作过程
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException
),子加载器才会尝试自己去加载。
3)为什么需要双亲委派模型?
在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码
4)怎么打破双亲委派模型?
打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。
7.垃圾收集器有哪些,及其原理
思路: 一定要记住典型的垃圾收集器,尤其是cms和G1,他们的原理和区别,设计的垃圾回收算法
1)几种垃圾收集器:
- Serial收集器: 单线程的收集器,针对新生代,收集垃圾时,必须stop the world,使用复制算法
- Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法
- ParNew收集器: 是Serial的多线程版本
- Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量,如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
- Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法
- CMS(Concurrent Mark Sweep)收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运行过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
- G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
2)CMS收集器和G1收集器的区别
- CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
- G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
- CMS收集器以最小的停顿时间为目标的收集器;
- G1收集器可预测垃圾回收的停顿时间
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
- G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片