1.JVM组成
JVM由那些部分组成,运行流程是什么?
JVM是Java程序的运行环境
组成部分:
类加载器:加载字节码文件到内存
运行时数据区:包括方法区,堆,栈,程序计数器,本地方法栈
执行引擎:执行字节码,优化代码
垃圾回收器:管理堆内存
运行流程:
加载字节码,准备运行环境,执行字节码,垃圾回收,程序结束
什么是程序计数器
用于记录每个线程正在执行的字节码指令的地址,用于保存字节码行号。当一个线程执行一段字节码到某个位置时,CUP使用权被另一个线程夺走,当前执行的地址会记录下来,当执行权回到当前线程时,会接着上次记录的位置继续执行
介绍Java堆
堆是一个线程共享的区域,主要用于保存对象实例,数组等,堆中内存不够会抛出OOM异常
堆区分为年轻代和老年代,年轻代被化为三部分,一个Eden区和两个S区,一个对象创建后会先到Eden区,如果对象被垃圾回收后还能存活就移动到S0或S1,再经过几次垃圾回收后还能存活则移动到老年代区中,老年代中存的是生命周期比较长的对象,
JDK1.7和1.8堆的区别
1.7中有一个永久代,存类信息,静态变量,常量,编译后代码,1.8移除了永久代,将数据存到本地内存中的元空间区,防止内存溢出
什么是虚拟机栈
每个线程运行时所需要的内存称为虚拟机栈,是一个先进后出结构,每个栈由多个栈帧组成,每个对应着每次方法调用时所占用的内存和数据,每个线程只能有一个活动栈帧,对应了当前正在执行的方法
垃圾回收是否设计栈内存
垃圾回收主要指堆内存,当栈帧弹栈以后,内存就会释放
栈内存分配越大越好吗
未必,默认的栈内存通常为1024k,栈帧过大会导致线程数减少
方法内的局部变量是否线程安全
如果方法内局部变量没有逃离方法的作用范围,那就是线程安全的,变量的创建和销毁都是在当前线程的虚拟机栈中完成的
如果局部变量引用了其他对象并逃离的方法的作用范围,那就要考虑线程安全
栈内存溢出情况
栈帧过多导致内存溢出,如递归调用
栈帧过大导致内存溢出
堆和栈的区别
栈内存一般用来存储局部变量和方法调用,堆用来存储Java对象和数组,堆会用垃圾回收,栈不会
栈内存时线程私有的,堆内存是线程共享的
异常错误不同,内存不足时,栈报StackOverFlow,堆报OutOfMemory
解释方法区
方法区主要存类的信息,运行时常量池,是各个线程共享的内存区域,在虚拟机启动时创建,虚拟机关闭时销毁
方法区在JDK 1.7时在堆区的永久代中,JDK1.8后取消了永久代,单独存在元空间中,避免了在堆区的OOM
解释一下运行时常量池
常量池可以看作一张表,虚拟机指令可以根据这张表找到要执行的类名,方法名,参数类型等信息
当类被加载时,常量池的信息就会放入运行时常量池,并把符号地址转为真实地址
直接内存
不属于JVM的内存结构,是虚拟机的系统内存,常见于NIO操作,用于数据缓冲区,直接内存相当于一块操作系统和Java代码都可以访问到的共享区域,比如我们在文件IO操作中,使用传统的BIO,需要调用操作系统的文件API,涉及到CPU用户态和内核态的切换,资源开销很大,引入直接内存之后,可以通过直接内存建立起系统内存和Java内存的交互传输
2.类加载器
什么是类加载器
类加载器的作用是将字节码文件加载到JVM中,从而使Java程序能够运行起来
类加载器有哪些:
启动类加载器,扩展类加载器,应用类加载器(用户自己编写的Java类),自定义类加载器
什么是双亲委派机制
加载一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,则继续向上委托,如果委托上级都没有加载,则子加载器尝试加载该类
JVM为什么采用双亲委派机制
可以避免一个类被重复加载,当父类加载后则无需重复加载,保证唯一性
为了安全,保证类库API不会被修改
类装载的执行过程
加载:根据类的全名获取类的字节码文件,将其转换为方法区内的运行时数据结构
验证:对字节码进行校验,确保符合JVM规范
准备:为类的静态变量分配内存,设置默认初始值
静态变量是final修饰的基本类型或字符串常量,赋值在准备阶段完成
静态变量是final修饰的引用类型,复制也初始化阶段完成
解析:将符号引用转为直接引用,即将类,方法,字段等解析为内存地址
初始化:执行类的初始化代码,包括静态变量赋值和静态代码块的执行
使用:JVM开始从入口方法执行用户的程序代码,如调用静态类的成员信息,使用new关键字创建对象实例
卸载:当用户程序代码执行完毕后,JVM开始销毁创建的Class对象
3.垃圾回收
简述垃圾回收机制
在Java语言中,有自动的垃圾回收机制,开发者只需要关注内存的申请,内存的释放由系统自动识别完成,不同的对象引用会有不同的回收机制
对象什么时候可以被垃圾回收器回收
如果一个对象或多个对象没有任何引用指向它了,那么这个对象现在就是垃圾,就有可能被GC回收
垃圾定位方法
引用计数法:一个对象被引用一次,则在当前对象头上递增一次引用次数,如果引用次数为0,则代表该对象可回收,如果出现循环引用的画计数法就会失效
可达性分析算法:会存在一个根节点,其引用指向下一个节点,依次向下类推,直到所有节点遍历完毕
判断如果某对象和根对象无直接或间接引用则可以被垃圾回收
JVM垃圾回收算法
标记清除算法:分标记和清除两个阶段,根据可达性算法通过GCRoot得出垃圾并进行标记,对这些标记为可回收的内容进行垃圾回收,效率高,有磁盘碎片,内存不连续
标记整理算法:标记清除的过程一样,但是会将清理后存活的对象都向内存的一端移动,然后清理边界之外的垃圾,无内存碎片,对象需要移动,效率低
复制算法:将原有的空间一分为二,每次只用其中一块,垃圾回收时,将正在使用的对象复制到另一块内存空间中,然后将当前空间清空,交换两块内存的角色,完成垃圾回收,无碎片,内存使用率低
JVM的分代回收
堆被分成两份,新生代和老年代(比例为1:2),对于新生代内部又分Eden区,两个幸存区:From,to
新创建的对象首先被分到Eden区,当Eden区内存不足时,标记Eden区和From区的存活对象并将其复制到to区中,复制完成后,Eden和from中的内存被释放,经过一段时间Eden区内存又不足,标记Eden和to区存活的对象,将存活的对象复制到from区,当幸存区对象熬过几次回收就会晋升到老年代
MinorGC、 Mixed GC 、 FullGC的区别是什么
MinorGC发生在新生代的垃圾回收,暂停时间短
MixedGC发生在新生代和部分老年代区域的垃圾回收。G1收集器持有
FullGC发生在新生代和老年代的完整垃圾回收,暂停时间长,应尽量避免
JVM有哪些垃圾回收器
串行垃圾回收器:垃圾回收时,只有一个线程在工作,其他线程都要阻塞等待垃圾回收完成。Serial作用于新生代,采用复制算法,Serial作用于老年代,采用标记整理算法
并行垃圾回收器:多个线程完成垃圾回收工作,其他线程同样阻塞,Parallel New用于新生代,采用复制算法,Parallel Old作用老年代,采用标记整理算法
CMS(并发)垃圾回收器:针对老年代进行垃圾回收,不会造成线程阻塞,会追踪标记整个GCRoot,将其直接关联和间接关联的对象
G1垃圾回收器,作用于新生代和老年代
详细聊一下G1垃圾回收器
用于新生代和老年代的垃圾回收,在JDK9后JVM默认用的G1垃圾回收,采用的回收算法是复制算法,分为三个阶段
首先G1将内存划分为多个区域,每个区域都可以充当Elen区,幸存者区。jumongous等
新生代回收:
初始时,所有区域处于空闲状态,创建了一些对象,挑出一部分空闲区左Eden区存储这些对象,当Eden区需要垃圾回收时,挑一个空闲区域做幸存区,用复制算法复制存活对象,需要暂停用户线程,再往后,Eden区内存又不足了,将Eden区以及幸存区的存活对象复制到新的幸存区,将较老的对象晋升至老年代
并发标记:
当老年代占用内存超过阈值(默认45%)后,触发并发标记,无需暂停用户线程,并发标记后会冲标解决漏标问题(此时需要暂停线程),这些都完成后就知道了老年代有哪些存活对象,之后进入混合手机阶段,此时不会堆老年代区域进行回收,而是根据暂停时间目标优先回收价值高的区域
混合收集:
复制完成,内存释放,进入下一轮垃圾回收,如果对象非常大,会开出Jupmgous区存储巨型对象
强引用,弱引用,软引用,虚引用
强引用:只要所有的GCRoots能找到,就不会被回收
软引用:需要配合SoftReference使用,当垃圾多次回收,内存依然不够用时会回收软引用对象
弱引用:需要配合WeakReference使用,只要进行垃圾回收,就会把弱引用回收
虚引用:必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放内存
4.JVM实践
JVM调优参数设置
war包部署在tomcat中设置
jar包部署在启动参数中设置
设置堆空间:-XMS(初始) -XMX(最大)
最大大小为默认物理内存的四分之一,初始大小时物理内存的六十四分之一
堆太小,会导致频繁的年轻代和老年代和垃圾回收,产生stw,用户线程阻塞,太大,可能会导致FullGC,会扫描整个堆空间,暂停用户线程的时间长
虚拟机栈设置:-XSS 每个线程默认1M
新生代中Eden区和两个幸存区的大小比例:默认8:1:1 -XXSus**=9 设置比例
年轻代晋升老年代的阈值: 默认为15,范围0-15
设置垃圾回收期
JVM调优工具
命令:
jps:查看进程状态信息
jstack:查看进程内线程堆栈信息
jmap:查询堆栈信息
jhatL堆转存快照工具
jstat:JVM统计监测工具
可视化工具:jconsole:JVM内存,线程,类监控 VisualVM:监控线程,内存情况
Java内存泄漏排查思路
使用jmap查询堆栈信息,生成dump文件
通过VisualVM,加载dump文件分析堆栈信息定位到代码排查
CPU跑到百分之百,解决思路是啥
通过top命令,定位到占用CPU高的线程
ps -T -p 进程ID找到进程中占比较高的线程
通过jstask查询线程的堆栈信息去定位代码