之前就准备总结一下常见的一些问题,可惜拖延症犯了,现在终于决定搞起来,先写第一篇,从jvm中常见的知识点说起来吧。
JVM
JVM通常情况下可以划分成五个区域:程序计数器,Java虚拟机栈,本地方法栈,堆,方法区(其中前三个区域各线程私有,相互独立,后面两个区域所有线程共享)。下边就是网上常见的内存区域划分图:
(这里的划分通常意义上是指HotSpot虚拟机,不同的虚拟机对内存区域的划分也不一样,同时同一个虚拟机不同版本也会有略微的区别,不过面试这么回答总是没有错的)
Java虚拟机栈
每当有java方法执行时,都会在虚拟机栈中创建一个栈帧,来存储局部变量表,操作数栈等,此区域会出现OOM等异常。
本地方法栈
类似于Java虚拟机栈,只不过运行的是Native方法。
程序计数器
程序计数器存储的是当前线程执行到的字节码的位置,当发生线程切换时,恢复线程就是从计数器里取出执行的位置。这块区域是不会发生OOM的。
Java堆
堆中存储了大部分的java对象实例和数组,是被所有线程共享的区域。也是发生垃圾回收的主要区域。
方法区
存储了被虚拟机加载的类型信息,常量,静态变量等数据,在JDK8以后,存储在方法区的元空间中(以前是存储在堆中的永久代中,JDK8以后已经没有永久代了)。
运行时常量池是方法区的一部分,会存储各种字面量和符号引用。具备动态性,运行时也可以添加新的常量入池(例如调用String的intern()方法时,如果常量池没有相应的字符串,会将它添加到常量池)(永久代,元空间这些也只针对于HotSpot虚拟机)
类加载
父类子类类加载顺序
这个基本上没有被问到过,但是还是需要知道,因为实际工作中可能会用到。
初始化父类的静态变量(如果是首次使用此类)
初始化子类的静态变量(如果是首次使用此类)
执行父类的静态代码块(如果是首次使用此类)
执行子类的静态代码块(如果是首次使用此类)
初始化父类的实例变量
初始化子类的实例变量
执行父类的普通代码块
执行子类的普通代码块
执行父类的构造器
执行子类的构造器
类加载的双亲委派机制
就是类加载器一共有三种:
启动类加载器:主要是在加载JAVA_HOME/lib目录下的特定名称jar包,例如rt.jar包,像java.lang就在这个jar包中。
扩展类加载器:主要是加载JAVA_HOME/lib/ext目录下的具备通用性的类库。
应用程序类加载器:加载用户类路径下所有的类库,也就是程序中默认的类加载器。
工作流程:
除启动类加载器以外,所有类加载器都有自己的父类加载器,类加载器收到一个类加载请求时,首先会判断类是否已经加载过了,没有的话会调用父类加载器的的loadClass方法,将请求委派为父加载器,当父类加载器无法完成类加载请求时,子加载器才尝试去加载这个类。 目的是为了保证每个类只加载一次,并且是由特定的类加载器进行加载(都是首先让启动类来进行加载)。
Java对象在内存中是如何存储的?
Java对象在内存中主要分为三部分:对象头、实例数据和对齐填充。
对象头:
对象头里主要包含对象自身的运行时数据(也就是图中Mark Word),类型指针(图中的Class Pointer,指向对象所属的类)。如果对象是数组,还需要包含数组长度(否则无法确定数组对象的大小)。
Mark Word:存储对象自身的运行时数据,例如hashCode,GC分代年龄,锁状态标志,线程持有的锁等等。在32位系统占4字节,在64位系统中占8字节。
Class Pointer:用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在开启了指针压缩时,占4字节。(默认是开启的)
Length:如果是数组对象,还有一个保存数组长度的空间,占4个字节。
实例数据:
保存的是对象的非静态成员变量数据,是真正的有效数据。
对齐填充:
因为HotSpot虚拟机的自动内存管理系统要求对象起始地址是8字节的整数倍,所以任何对象的大小必须是8字节的整数倍,而对象头部分一般是8字节的倍数,如果实例数据部分不是8字节的整数倍,需要对齐填充来补全。
垃圾回收
垃圾回收算法:
1.标记清除算法:会产生内存碎片且执行效率不稳定;常用于老年代
2.标记整理算法:移动过程需要STW,且整理时会改变对象的引用地址;常用于老年代
3.标记复制算法:无法100%利用内存空间,会造成一定的空间浪费。常用于新生代
4.分代回收算法:理论依据是分代假说,弱分代假说:绝大多数对象都是朝生夕灭的。
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
垃圾回收器:
常问的是CMS。
1.初始标记(STW)
2.并发标记(可以和用户线程并发执行)
3.重新标记(STW)
4.并发清除
G1垃圾收集器:
与CMS不同之处是将内存区域分成很多内存块,每个内存块可以担任eden,survivor,old,humongous中的任一角色,进行垃圾回收时也是由垃圾回收器判定最优的回收区域,来满足用户设定的垃圾回收时间。
1.初始标记(STW)
2.并发标记(可以和用户线程并发执行)
3.最终标记(STW)
4.筛选回收(STW)
涉及到跨代引用,垃圾回收器使用了卡表和remember set来进行处理,不展开讲了。
还有三色标记法会产生浮动垃圾(多标的情况),还会少标(增量更新法-CMS,原始快照法-G1)