目录
1、Java类加载器
- 在JDK中,它自带了三个类加载器分别是:BootStrapClassLoader(启动类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)。
- BootStrapClassLoader(启动类加载器):它是ExtClassLoader的父类加载器,默认的加载方式是在%JAVA_HOME%lib目录下的jar包和class文件。
- ExtClassLoader(扩展类加载器):它是AppClassLoader的父类加载器,它负责加载%JAVA_HOME%lib/ext文件夹下的jar包和class类。
- AppClassLoader(应用类加载器):它是自定义加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下文加载器。
- 它们全部都继承了ClassLoader并且实现了自定义类加载器。
2、双亲委托模型
从父类加载器到子类加载器分别为:
- BootStrapClassLoader 加载路径为:JAVA_HOME/jre/lib
- ExtensionClassLoader 加载路径为:JAVA_HOME/jre/lib/ext
- ApplicationClassLoader 加载路径为:classpath
还有一个自定义类加载器
- 当一个类加载器收到类加载请求时,会先把这个请求交给父类加载器处理,
- 若父类加载器找不到该类,再由自己去寻找。
- 该机制可以避免类被重复加载,还可以避免系统级别的类被篡改
双亲委托模型的一个过程:
如果一个类加载器收到了类加载器的请求,首先它不会自动得去尝试加载这个类,它会把这个类托付给父类加载器去完成,每一层依次都是这样。因此所有的加载请求都会被传送到顶层的启动类加载器(BootStrapClassLoader)中。只有当父类加载器反馈自己无法完成加载请求的时候,其实就是找不到类的时候,这个时候子类加载器才会尝试自己去加载,这个过程就叫做双清委托模型。
优点:
- 主要是为了安全,避免了用户自己编写的类动态替换了Java的一些核心类,比如String
- 同时也避免了类的重复加载(因为在JVM中区分不同类,不单是根据类名,在相同的class文件中被不同的ClassLoader加载就是不同的两个类)
3、GC如何判断对象可以被回收
引用计数法:
- 其实就是每个对象都有一个引用计数属性,新增一个引用时计数的时候会加1,引用释放时计数的时候会减1,计数为0的时候就可以回收
其实简单点就是已淘汰,为每个对象添加引用计数器,引用为0时判定可以回收,会有两个对象相互引用无法回收的问题
可达性分析法:
- 从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。
- 当一个对象到 GC Roots 没有任何引用链相连的时候,就可以证明此对象是不可用的,那么虚拟机就判断是可回收对象。
其实简单点就是从GCRoot开始往下搜索,搜索过的路径称为引用链,若一个对象GCRoot没有任何的引用链,则判定可以回收
引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算它们都不再使用了,但因为相互引用计数器=1,也是永远无法被回收。
GC Roots的对象有:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(其实就是一般说的Native方法)引用的对象
简单点就是虚拟机栈中引用的对象,方法区中静态变量引用的对象,本地方法栈中引用的对象(好理解!)
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。
对象被系统宣告死亡至少还要经历两次标记过程:
- 第一次是经过可达性分析发现没有与GC Roots相连接的引用链,
- 第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
- 当对象变成(GC Roots)不可达的时候,GC会判断该对象是否覆盖了finalize方法,
- 如果没有覆盖,那么就直接将其回收。
- 如果覆盖了,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。
- 执行finalize方法完毕后,GC会再次判断该对象是否可达,
- 若不可达的话,就会进行回收,
- 如果可达的话,对象就会“复活”
- 每个对象只能触发一次finalize()方法
- 由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。
4、JVM运行时数据区(内存结构)
线程私有区:
虚拟机栈:
- 每次调用方法都会在虚拟机栈中产生一个栈帧,
- 每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧
本地方法栈:
- 它为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一
程序计数器:
- 保存指令执行的地址,方便线程切回后能继续执行代码
线程共享区:
堆内存:
- Jvm进行垃圾回收的主要区域,存放对象信息,分为新生代和老年代,内存比例为1:2,
- 新生代的Eden区内存不够时时发生MinorGC,老年代内存不够时发生FullGC
方法区:
- 存放类信息、静态变量、常量、运行时常量池等信息。
- JDK1.8之前用持久代实现,JDK1.8后用元空间实现,元空间使用的是本地内存,而非在JVM内存结构中
5、什么情况下会内存溢出?
堆内存溢出:
- 当对象一直创建而不被回收时
- 加载的类越来越多时
- 虚拟机栈的线程越来越多时
栈溢出:
- 方法调用次数过多,一般是递归不当造成
6、JVM有哪些垃圾回收算法?
标记清除算法:
- 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。
复制算法:
- 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。(用在新生代)
标记整理算法:
- 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。(用在老年代)
7、典型垃圾回收器
CMS:
- 以最小的停顿时间为目标、只运行在老年代的垃圾回收器,使用标记-清除算法,可以并发收集。
G1 :
- JDK1.9以后的默认垃圾回收器,注重响应速度,支持并发,
- 采用标记整理+复制算法回收内存,使用可达性分析法来判断对象是否可以被回收。
8、JVM中有哪些引用?
强引用:
- new的对象。哪怕内存溢出也不会回收
软引用:
- 只有内存不足时才会回收
弱引用:
- 每次垃圾回收都会回收
虚引用:
- 必须配合引用队列使用,一般用于追踪垃圾回收动作
9、类加载过程
(1)加载 :
- 把字节码通过二进制的方式转化到方法区中的运行数据区
(2)连接:
验证:
- 验证字节码文件的正确性
准备:
- 正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了
解析:
- 将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)
(3)初始化 :
- 执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块。
10、JVM类初始化顺序
父类静态代码块和静态成员变量 - > 子类静态代码块和静态成员变量 - > 父类代码块和普通成员变量 - > 父类构造方法 - > 子类代码块和普成员变量 - > 子类构造方法
11、对象的创建过程
- 检查类是否已被加载,没有加载就先加载类
- 为对象在堆中分配内存,使用CAS方式分配,防止在为A分配内存时,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存。
- 初始化,将对象中的属性都分配0值或null
- 设置对象头
- 为属性赋值和执行构造方法
12、对象头中有哪些信息
- 对象头中有两部分,
- 一部分是MarkWork,存储对象运行时的数据,如对象的hashcode、GC分代年龄、GC标记、锁的状态、获取到锁的线程ID等;
- 另外一部分是表明对象所属类,如果是数组,还有一个部分存放数组长度
13、JVM内存参数
- -Xmx[]:堆空间最大内存
- -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的
- -Xmn[]:新生代的最大内存
- -xx:[survivorRatio=3]:eden区与from+to区的比例为3:1,默认为4:1
- -xx[use 垃圾回收器名称]:指定垃圾回收器
- -xss:设置单个线程栈大小
一般设堆空间为最大可用物理地址的百分之80
14、GC的回收机制和原理
- GC的目的实现内存的自动释放,使用可达性分析法判断对象是否可回收,
- 采用了分代回收思想,将堆分为新生代、老年代,新生代中采用复制算法,老年代采用整理算法,
- 当新生代内存不足时会发生minorGC,老年代不足时会发送fullGC