Java高级面试-GC相关
一、classloader
1.什么是类加载器
ClassLoader就是用来动态加载class文件到内存当中用的
2.类加载类型(站在程序员角度)
BootStrap ClassLoader:启动类加载器,由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类
Extension ClassLoader:扩展类加载器,负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。
App ClassLoader:应用程序类加载器,负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有负责加载用户路径下
(站在Java虚拟机角度)
BootStrap ClassLoader:启动类加载器
其他类加载器: 由Java语言实现,继承自抽象类ClassLoader。
3.双亲委托模型
工作流程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
好处:不同层次的类加载器具有不同优先级,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。
4.类加载的过程
验证,准备,解析3个部分统称为连接
加载,验证,准备,初始化,卸载这5个阶段的顺序是确定的,而解析阶段则不一定:它在某些情况下可以在初始化完成后在开始,这是为了支持Java语言的运行时绑定。
其中加载,验证,准备,解析及初始化是属于类加载机制中的步骤。注意此处的加载不等同于类加载。
具体过程:
加载:
①.通过一个类的全限定名来获取定义此类的二进制字节流
②.将这个字节流所代表的静态存储结构转换为方法区内的运行时数据结构
③.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证:
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
文件格式验证、元数据验证、字节码验证、符号引用验证
准备:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。
1)对基本数据类型,不显示其赋值则系统默认赋值,对局部变量使用前赋值
2)对于同时被static和final修饰的常量,在声明的时候就要赋值,只被final,要在类初始化之前赋值
3)对于引用数据类型reference,没有对其赋值而直接引用,则为默认值null
4)在数组初始化时,没有对各元素赋值,根据数据类型被赋予对应的零值
解析:
虚拟机将常量池内的符号引用替换为直接引用的过程。
1)类或接口的解析
2)字段解析
3)类方法解析
4)接口方法解析
初始化:
初始化阶段是执行类构造器<clinit>()方法的过程。
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。
<clinit>()与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。
简单地说,初始化就是对类变量进行赋值及执行静态代码块。
三、java堆/栈
1.Java程序运行时的内存分配策略
1)静态存储区(方法区):主要存放静态数据、全局static数据和常量
2)栈区:方法体内的局部变量都在栈上创建
3)堆区:通常就是指在程序运行时直接new出来的内存
2.栈内存/堆内存的区别
在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。
堆内存用来存放所有由new创建的对象(包括该对象其中的所有成员变量)和数组。在堆中分配的内存,将由Java垃圾回收器来自动管理
3.Java内存回收机制
4.Java内存泄漏引起的原因
长生命周期的对象持有端生命周期对象的引用就很可能发生内存泄漏
总结:
堆:运行时,垃圾回收,动态分配内存,存取速度漫
栈:存取速度慢,生命周期缺乏灵活性