1. 说说JVM的内存模型以及分区
JVM内存区域分为栈、堆、方法区
栈(线程私有)
虚拟机栈:java方法执行时会创建一个栈帧,用于存储**局部变量表、操作数栈、动态链接、方法出口**
局部变量表:存放方法参数和方法内定义的局部变量
操作数栈:保存计算过程中间结果,同时作为计算过程中变量临时的存储空间
动态链接:将方法调用的符号引用转化为方法的直接引用
方法出口:返回方法被调用的位置,继续执行下面的程序
本地方法栈:本地方法栈和虚拟机栈类似,Java 虚拟机栈是给 JVM 使⽤的,本地⽅法栈是给本地⽅法使⽤的
程序计数器:记录当前线程执行的行号
堆(线程共享):堆主要用于存放各种类的实例对象和数组
方法区(线程共享):主要是存储类信息,常量池(static 常量和 static 变量),编译后的代码(字节码)等数据
2.说说JVM中的堆内存
堆是JVM中最大的一块内存。在java中被分为两个区域:年轻代和老年代(java8 取消了永久代,采用了 Metaspace)。
- 新生代
1.新生代采用的是复制算法。 2.新生代先分为eden,s0,s1三块区域,新对象一般都分配在eden区域。 3.如果eden区域满了触动垃圾回收,就把eden存活的对象复制到S0区域,然后清除掉eden区域。 4.在下一次垃圾收集时,如果eden区域满了,S0和eden区域的存活对象会被复制到S1中,然后eden和S0会被清空。 5.在这个过程中,幸存的对象会随着每次触动垃圾收集,年龄也在增长。当这些存活的对象年龄达到了新生代年龄阈值(HotSpot默认的垃圾回收是15次),就会进入到下一个区域,老年代。
- 老年代
老年代中使用“标记-清除”或者“标记-整理”算法进行垃圾回收,回收次数相对较少,每次回收时间比较长。在垃圾收集整个过程中,不管是处于新生代,还是老年代,都必须记住**Stop the World 事件** ,就是说在这种事件发生时,所有的程序线程都要暂停,直到事件完成(比如这里就是完成了所有回收工作)为止。Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,因为它涉及到所有存活对象。所以,对于响应性的应用程序,应该尽量避免Major GC。
- 永久代(java8之后没有了,改为Metaspace)
包含JVM用于描述应用程序中类和方法的元数据。永久代是由JVM在运行时根据应用程序使用的类来填充的。如果JVM发现某些类不再需要,并且其他类可能需要空间,则这些类可能会被回收。
3. 说说JVM类加载过程
JVM 的类加载分为五个阶段:
4. JVM类加载器有哪几种
- 引导类加载器BootStrap ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下核心类库。
- 扩展类加载器Extension ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下的 ext扩展目录中的JAR包
- 应用类加载器Application ClassLoader:负责加载ClassPath路径下的类包,主要就是加 载你自己写的那些类
- 自定义类加载器User ClassLoader:负责加载用户自定义路径下的类包
5. 什么是双亲委派模型
首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);)。或者是调用bootstrap类加载器来加载。
如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
优点:
安全性:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
唯一性:采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的引导类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象
6.垃圾回收时如何判断一个对象是否死亡?
- 引用计数法
引用计数描述的算法为: 给对象增加一个<font color=yellow>引用计数器</font>,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。缺点:无法解决对象的循环引用问题
- 可达性分析算法:
将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾 对象,其余未标记的对象都是垃圾对象。在Java语言中,可作为GC Roots的对象包含下面几种:(栈,方法区)
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(Native方法)引用的对象
7. 垃圾回收算法有哪些?
标记-清除算法
算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
优点:
- 简单
缺点:
- 效率低
- 内存碎片问题
复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一 块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
优点:
- 性能⾼
缺点:
- 内存利用率低
标记-整理算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
优点:
- 不会产生内存碎片
缺点:
- 标记-整理算法要低于复制算法,移动过程中,需要全程暂停用户应用程序。即:STW。
分代收集算法:
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。
8. 说说Minor GC、Major GC、Full GC
JVM在进行GC时,并非每次都对三个内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆回收(Full GC)
部分收集: 不是完整收集整个Java堆的垃圾收集。其中又分为:
新生代收集(Minor GC / Young GC): 只是新生代(Eden \ S0,S1)的垃圾收集
老年代收集(Major GC / Old GC): 只是老年代的垃圾收集
混合收集(Mixed GC): 收集整个新生代以及部分老年代的垃圾收集
整堆收集(Full GC): 收集整个Java堆和方法区的垃圾收集