【八股文】JVM
1.简述Java类加载机制
2.类加载器类别
3.双亲委派机制
4.沙箱安全机制
5.如何打破双亲委派机制
6.讲述一下JVM内存结构
7.创建对象的方式
8.创建对象的步骤
9.对象的内存布局
10.对象访问定位
11.如何判断对象已经死亡
12.垃圾回收算法
13.常见的垃圾回收器
14.JVM调优命令
1.简述Java类加载机制
在此之前,先解释一下什么是类的加载:
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构。
加载,链接,初始化,链接又包括验证,准备,解析
- 加载:将字节码文件中的.class文件,通过类加载器,加载进运行时数据区的方法区,并创建一个大的Class对象
- 验证:确保Class文件的字节流中包含的信息符合虚拟机规范的全部约束要求
- 准备:为类的静态变量分配内存并初始化为默认值(指的是系统的,不是代码的)
- 解析:Java虚拟机将常量池内的符号引用替换成直接引用的过程(在java中,一个java类将会编译成一个class文件,在编译时,java类并不知道所引用的类的实际地址,因此只能用符号引用代替。因为各个虚拟机实现的内存布局可能有所不同,但他们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在java虚拟机规范的Class文件格式中),解析包括动态解析和静态解析。动态解析就是指所引用的类可能是接口或者抽象类,并不明确,不知道使用哪个具体类的直接引用进行替换。等到运行过程中发生调用,虚拟机栈得到了具体的类型信息,此时再进行解析。
- 初始化:真正执行类中编写的java程序代码,执行clinit()
2.类加载器类别
- 启动类加载器:最顶层的加载器,由C++实现,嵌套在JVM内部,负责加载Java的核心库(%JAVA_HOME%/lib目录下的jar包和类或者被-Xbootclasspath参数指定的路径中的所有类)
- 扩展类加载器:主要负责加载目录%JRE_HOME%/lib/ext目录下的jar包和类,或被java.ext.dirs系统变量所指定的类库
- 引用程序类加载器:面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类
- 自定义类加载器:我们自己编写的类的加载器
3.双亲委派机制
1)如果一个类加载收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类加载器去执行
2)如果父类加载器还存在其父类加载器,则进一步向上委托,直到达到最顶层的启动类加载器
3)如果父类加载器可以完成类的加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载
4.沙箱安全机制
沙箱:一个限制程序运行的环境
沙箱机制:将java代码限定在JVM特定的范围执行,并且严格限定代码对本地资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏
5.如何打破双亲委派机制
重写ClassLoader的loadClass()方法
6.讲述一下JVM内存结构
- 程序计数器:用来记录当前线程所执行的字节码的行号指示器,为了线程切换后能恢复到正确的执行位置
- 虚拟机栈:由一个个的栈帧组成,栈帧包括局部变量表,操作数栈,动态链接,方法出口
- 堆空间:存放对象实例,堆空间包括新生代(eden,s0,s1),老年代,元空间
对象优先进入eden区,经历了一次minor gc后仍然能存活,并且能够被survivor容纳的话,就会被移动到survivor,并且将对象年龄设置为1,并每经历过一次minor gc年龄就会增长1,到了一定程度(默认为15)就会晋升到老年代,另外,大对象直接进入老年代
- 方法区:用来存储已经被虚拟机加载的类信息,常量,静态变量,即时编译后的代码
- 本地方法栈:用来执行本地方法
- 运行时常量池:方法区的一部分,在类加载后,会将编译器生成的各种字面量和符号引用放到运行时常量池。
- 直接内存:不属于JVM范畴,NIO的Buffer提供了directBuffer,可以直接访问系统物理内存,避免堆内内存访问堆外内存的数据拷贝,提高效率。对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到直接内存。
7.创建对象的方式
- new
- 反射,newInstance(),这种方式要求该Class对象的对应类有默认的构造器
- 反射,先使用Class对象获取指定的构造器,调用构造器的newInstance()
- clone()
- 反序列化:从文件中/网络中获取一个对象的二进制流
- 调用第三方库Objenesis
8.创建对象的步骤
1)检查是否进行了类的加载
2)为对象分配内存(计算对象占用空间大小,在堆中划分一块内存给新对象)
3)处理并发问题(采用CAS失败重试,区域加锁保证更新的原子性)
4)初始化分配到的空间(所有属性设置默认值)
5)设置对象头(存储信息)
6)执行init方法初始化
9.对象的内存布局
- 对象头
运行时元数据:包括哈希值,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳
类型指针:指向类元数据InstanceClass,确定该对象所属的类型
- 实例数据
他是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来和本身拥有的字段)
- 对齐填充
10.对象访问定位
句柄访问和直接访问
11.如何判断对象已经死亡
- 引用计数法
对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况
缺点:如果对象出现互相引用,则无法回收
- 可达性分析
思路:可达性分析算法是以根对象集合为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
GC Roots根集合就是一组必须活跃的引用,可以是:
1)虚拟机栈中引用的对象
各个线程被调用的方法中使用到的参数,局部变量
2)本地方法栈内JNI引用的对象
3)方法区中类静态属性引用的对象
java类的引用类型静态变量
4)方法区中常量引用的对象
字符串常量池(String Table)里的引用
5)所有被同步锁synchronized持有的对象
6)java虚拟机内部的引用
基本数据类型对应的class对象,一些常驻的异常对象
7)本地代码缓存
12.垃圾回收算法
垃圾回收分为两步,标记阶段和清除阶段
标记阶段就是判断对象存亡的过程
清除阶段:进行垃圾回收
- 标记-清除算法
两次遍历,第一次标记,第二次清除,容易碎片化
- 复制算法
将内存空间分为两块,在垃圾回收的时候将正在使用的内存中的存活对象复制到未被使用的内存块
- 标记-压缩算法
在自己的内存区域排序
13.常见的垃圾回收器
先了解一下相关概念
按线程数分:
- 串行垃圾回收器:只有一个线程,指的是同一时间段只允许有一个CPU用于执行垃圾回收,此时工作线程暂停
- 并行垃圾回收器:多个线程同时执行GC,出现STW,应用程序暂停
按工作模式分:
- 并发式垃圾回收器:与应用程序同时工作
- 独占式垃圾回收器:停止应用程序中的所有用户线程,直到垃圾回收结束
1)Serial回收器:串行回收
Serial收集器采用复制算法,串行回收和STW机制的方式执行内存回收
Serial Old收集器同样采用了串行回收和STW机制,内存回收算法使用的是标记–压缩算法
使用场景:简单而高效,多用于内存不大,可以在短时间完成垃圾收集,不频繁发生的场景。
2)ParNew回收器:并行回收
是Serial的多线程版本
除了并行串行的区别,几乎没有其他区别
3)Parallel Scavenge回收器:吞吐量优先
复制算法,并行回收,STW机制
于ParNew不同的是,Parallel Scavenge收集器的目的则是达到一个可控制的吞吐量
还有一个区别就是Parallel Scavenge支持自适应调节
使用场景:高吞吐量,适合后台运行,不需要太多交互的任务
Parallel Old收集器采用了标记–压缩算法,但同样也是基于并行回收和STW机制
4)CMS回收器:低延迟
CMS流程:
1)初始标记
在这个阶段中,程序中所有的工作线程都会因为STW机制而暂停,这个阶段的主要任务就是仅仅只是标记出GC Roots能直接关联出的对象
2)并发标记
从GC Roots的直接关联对象开始遍历整个对象图的过程,进行可达性分析,这个过程耗时较长但不需要停顿用户线程
3)重新标记
修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
4)并发清除
此阶段清除删掉标记成垃圾的对象
优点:并发收集,低延迟
缺点,使用的标记清除算法,容易碎片化
5)G1回收器:区域分代化
G1设定的目标就是在延迟可控的情况下获得尽可能高得吞吐量,所以才担当起“全功能收集器”得重任和期望
G1是一个并行回收器,它把堆内存分割为很多不相关得区域(region)。使用不同的region来表示Eden,s0,s1,老年代
G1 GC会有计划的避免在整个java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次跟据允许的收集时间,优先回收价值最大的region
G1的特点:
- 兼具并行和并发
并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力
并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行
- 分代收集
兼顾老年代和年轻代,和region相关,eden,s0,s1物理上可以不连续
- 空间整合
内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记–压缩算法
这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当java堆非常大的时候,G1的优势更加明显
- 可预测的停顿时间模型
软实时:有90%的把握能保证10ms内,简单点就是说基本能把握
由于是跟据价值大小进行GC
总结
14.JVM调优命令
jps:列出本机所有Java进程的进程号
常用参数如下:
-m:输出main方法的参数
-l:输出完全的包名和应用主类名
-v:输出JVM参数
jstack:查看某个Java进程内的线程堆栈信息。使用参数-l可以打印额外的锁信息,发生死锁时可以使用jstack -l pid观察锁持有状态
jstat:用于查看虚拟机各种运行状态信息(类装载,内存,垃圾收集等运行数据)。使用参数-gcuitl可以查看垃圾回收的统计信息
jmap:查看堆内存快照。通过jmap命令可以获取运行中的堆内存的快照。从而可以对堆内存进行离线分析
本专栏是我总结的八股大全