1.什么是类加载?
类加载即将字节码文件读取到内存,存放在运行时数据区的方法区内,并且在堆中创建一个java.lang.Class对象,这个对象封装了方法区的数据结构。类加载的最终产品是堆中的Class对象,提供了访问方法区内的数据结构的接口。
2.类加载的方式?
- 从本地系统中加载;
- 网络下载;
- 从zip,jar归档文件中加载;
- 从专有数据库加载;
- 将java源文件编译成.class文件
3.类的生命周期?
加载 连接(验证,准备,解析)初始化
- 加载:将字节码文件加载进内存,保存在运行时数据区的方法区内,并在堆区创建Class对象;
- 验证:检查字节流中包含的信息是否符合jvm的要求;
- 准备:给静态变量分配内存并赋予一个默认初始值;
- 解析:将类中的符号引用转化为直接引用,直接引用就是直接指向目标的指针、相对偏移量或间接定位到目标的句柄,符号引用就是一组符号来描述目标,可以是任何字面量。
- 初始化:给静态变量赋予正确的初始值;
4.类加载器?
- 启动类加载器:负责加载JAVA_HOME/lib路径下或通过-Xbootclass path定义的路径下且被虚拟机认可的类;
- 扩展类加载器:负责加载JAVA_HOME/lib/ext路径下或通过系统变量java.ext.dir指定的路径下的类;
- 应用程序类加载器:负责加载用户路径下的类;
- 用户自定义类加载器:用户可以通过继承java.lang.ClassLoader来实现自定义类加载器。
5.JVM类加载机制?
- 全盘负责:
- 父类委托:
- 缓存:
6.双亲委派模型?
一个加载器接收到类加载请求之后,首先本身先不进行加载,而是将请求委托给父类进行加载,依次向上,因此所有的类加载都会传到启动类加载器那里,只有当父类加载器无法加载时(类路径下找不到对应的类),子类加载器才会自己去加载该类。
双亲委派模型的好处:
- 每一个类都只会被加载一次,避免了重复加载;
- 每一个类都会被尽可能的加载(从引导类加载器往下,每个加载器都可能会根据优先次序尝试加载它);
- 有效避免了某些恶意类的加载(比如自定义了Java.lang.Object类,一般而言在双亲委派模型下会加载系统的Object类而不是自定义的Object类)
7.jvm的组成?
jvm有两个子系统,两个组件;
两个子系统为:类加载器,执行引擎
两个组件为:运行时数据区,本地接口库
8.jvm内存结构
- 程序计数器:指的是执行的字节码文件的行号指示器,线程私有,是内存中唯一一个没有oom的区域;
- Java虚拟机栈:用于描述方法执行时的内存结构,当执行一个方法的时候,都会创建一个帧栈,用于存储局部变量表,操作数栈,动态链接,方法入口等。一个方法的执行到结束,对应着帧栈的入栈和出栈;也是线程私有的。
- 本地方法栈:和java虚拟机栈类似,只是服务的对象是native方法;
- 堆:用于给类的对象和数组分配内存的区域,是线程共享的。也是GC的主要区域,从GC的角度还可以分为新生代和老年代。
- 方法区:又称为老年代,用于存储被jvm加载的类信息,静态变量,常量,即使编译器编译后的代码。这一部分的垃圾回收主要在于常量池的回收和类型的卸载。
9.堆和栈的区别?
- 物理地址:栈是连续的,因此效率会比较高;堆是不连续的,效率低;
- 内存分别:堆不连续,所以分配的内存在运行期确认,因此大小不固定;而栈连续,内存在编译期间就确认,因此大小确定。
- 存放的内容:堆存放的是对象实例,数组,更关注数据的存储;而栈存储的是局部变量,操作数栈,返回结果等,更关注方法的执行。
- 程序的可见度:堆对于整个应用程序都是共享可见的,而栈只是对于线程可见,所以也是线程私有的,生命周期和线程相同。
10.如何确定垃圾?
-
引用计数法
如果要操作一个对象必须要有引用,一个简单的办法就是通过引用计数判断一个对象是否可以回收 -
可达性分析
如果在GC-Roots和一个对象之间没有可达路径,则称对象是不可达的,但是不可达对象不等价于客户收对象,不可达对象变为可回收对象至少要经过两次标记。
11.垃圾清除算法
- 标记清除法:
- 复制算法
- 标记整理算法
- 分代收集算法
12.垃圾收集器
- Serial垃圾收集器
- ParNew垃圾收集器
- Parallel Scavenge收集器
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
- G1收集器
13.Java中的四种引用类型
- 强引用
- 软引用
- 弱引用
- 虚引用
14.JVM8为什么要增加元空间,带来什么好处?
原因:
- 字符串在永久代中容易出现内存溢出和性能问题;
- 类及方法的信息等难以确定大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大容易造成老年代溢出;
- 永久代会为GC带来不必要的复杂度,回收效率比较低;
好处:
- 每个加载器有专门的空间;
- 不会单独回收某个类;
- 元空间里的对象的位置是固定的;
- 如果发现某个类加载器不再存活了,会把相关的空间整个回收。
15.如何解决线上gc频繁的问题?
- 查看监控,以了解出现问题的时间点以及当前FGC的频率(可对比正常情况看频率是否正常)
- 了解该时间点之前有没有程序上线、基础组件升级等情况。
- 了解JVM的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃 圾收集器,然后分析JVM参数设置是否合理。
- 再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法 比较容易排查。
- 针对大对象或者长生命周期对象导致的FGC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象。
- 通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑 对象是否满足了进入到老年代的条件才能下结论。
16.堆G1垃圾收集器有了解么,有什么特点
- G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
- G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
- G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
- G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
- 因为G1建立可预测的停顿时间模型,所以每一次的垃圾回收时间都可控,那么对于大堆(16G左右)的垃圾收集会有明显优势.