文章目录
什么是程序计数器
程序私有的,内部保存的字节码的行号,用于记录正在执行的字节码指令的地址。
介绍下Java堆
java堆就是线程共享的区域,主要用来保存对象实例、数组等,当堆中没有内存空间可分配也无法扩展的时候就会抛出OOM异常
组成:年轻代+老年代
年轻代被划分为三部分,Eden区和两个相同的Survior区(幸存者区)
老年代主要保存生命周期长的对象
内存结构在Java7与8的区别
在java8之后将原有堆中的方法区/永久代(主要保存了类信息、常量、编译后的代码) 放在了本地内存的元空间中,原因是避免OOM,放在内存溢出
什么是虚拟机栈
每个线程运行时所需要的内存,被称为虚拟机栈,特点是先进后出
每个栈由多个栈帧组成,就是每次方法调用时所占用的内存
垃圾回收是否涉及栈内存
垃圾回收就是指堆内存当栈帧弹栈之后内存就会释放
栈内存分配越大越好吗
不是,默认是1024k,栈帧过大会导致线程数变少,通常就是用默认的
方法内的局部变量是否是线程安全的
- 如果方法内局部变量只作用于方法内,他是线程安全的
- 但如果局部变量引用了对象,并逃离了方法的作用范围,此时需要考虑线程安全
什么情况下栈内存溢出
- 栈帧过多导致栈内存溢出,典型问题:递归
- 栈帧过大导致栈内存溢出(一般不会出现,也没有遇到过)
堆栈的区别是什么
- 栈内存一般用来存储局部变量和方法调用的,堆内存是用来存储Java对象和数组的,堆会GC垃圾回收,栈不会
- 栈内存是线程私有的,堆内存是线程共有的(需要考虑线程安全问题)
- 两者抛出异常错误也不同,
栈内存不足是SOF、堆内存不足是OOM
解释下方法区
方法区Method Area 是各个线程共享的内存区域,主要存储类的信息、运行时常量池
介绍下运行时常量池
当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
常量池是可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名等信息;
什么是类加载器
JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中从而让Java程序能够启动
类加载器有那些
- 启动类加载器、扩展类加载器、应用类加载器、自定义类加载器
什么是双亲委派模型、JVM为什么采用双亲委派
双亲委派模型就是加载某一个类时,先委托上一级的加载器进行加载,如果上级加载器也有上级,就会继续向上委托,如果该类委托上级没有被加载,子类加载器就会尝试加载该类
至于JVM为什么采用双亲委派是因为避免某个类被重复的加载,当父类已经加载后则无需重复加载,保证唯一性,也能够保证类库API不会被更改
类装载的执行过程
加载-验证-准备-解析=初始化-使用-卸载
对象什么时候可以被垃圾器回收
如果一个或多个对象没有任何的引用指向它,那么这个对象就会被定位为垃圾,就可能被垃圾回收器回收
定位垃圾的方式有两种:
- 引用计数法(当对象出现循环引用时,会出现内存泄漏风险)
- 可达性分析算法(现在虚拟机都采用的此方法)
扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,如果不能找到就表示可以被回收
那些对象可以作为GC Root
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
都指向堆中的一块区域
垃圾回收算法有那些
- 标记清除算法:
分为2个阶段:标记和清除
根据可达性分析算法得出得垃圾进行标记
对这些标记可回收的内容进行垃圾回收
标记和清除速度块,但碎片化严重,内存不连贯 - 标记整理算法
与标记清除算法差不,将存活对象都向内存另一端移动,然后清理边界以外的垃圾,无碎片,但对象需要移动,效率较低 - 复制算法
在垃圾对象多的情况下,效率比较高,并且清理后内存无碎片化的,但是它需要分配2块内存空间,在同一时刻,只能使用一半,内存使用率低
JVM的分代回收
首先将新创建的对象,都会分配到eden区
之后当eden区内存不足时,标记eden与from的存活对象,将存活的对象采用复制算法复制到to中,复制完毕清除eden和from内存
经过一段时间后eden的内存又出现不足,就会标记eden和to区域存活的对象,并复制到from区
当幸存区对象被来回的回收多次之后,就会晋升到老年代(辛存区内存不足或大对象会提取晋升)
堆的区域划分
- 堆被划分为两份:新生代和老年代【1:2】
- 对于新生代,内部又被划分为三个区域:eden伊甸园区和survivor幸存者区(分为from和to)【8:1:1】
MinorGc、MixedGc、FullGc区别
MinorGc:young Gc 发生在新生代的垃圾回收,暂停时间短(STW)
MixedGc:新生代+老年代部分区域的垃圾回收,G1收集器特有
FullGc:新生代+老年代完整垃圾回收,暂停时间长(STW),尽量避免
JVM垃圾回收器有那些
- 串行垃圾收集器:SerialGC、Serial Old GC
- 并行垃圾收集器:Paraller Old GC、ParNew GC
- CMS(并发)垃圾收集器:CMS GC作用在老年代
- G1垃圾收集器:作用在新生代和老年代
概述G1垃圾回收器
G1垃圾回收器应用在新生代和老年代中,在JDK9之后垃圾回收器默认使用G1,
G1垃圾回收器划分了多个区域,每个区域都可充当eden、survivor、old、humongous,其中humongous存储大对象的
G1垃圾回收器分为三个阶段:
1.新生代回收STW
2.并发标记,就是重新标记STW
3.混合收集
采用复制算法,如果并发失败,会触发full GC
强软弱虚
强引用: 只要能被GC Root找到就是强引用,平时new的也是强引用。强引用的对象,即使出现OOM也不会对该对象进行回收,死都不收
软引用: 需要用SoftReference类来实现,软引用在内存够用时就会保留,不够用时就回收
弱引用: 需要用WeakReference类来实现,只要垃圾回收机制运行,不管JVM内存是否足够,都会回收
虚引用: 必须配合引用队列联合使用,被引用的对象回收时,会被虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存
JVM调优参数可以在哪里设置参数值
Xms 堆的初始化大小 Xmx最大大小
-
war包部署在tomcat中设置
修改TOMCAT_HOME/bin/catalina.sh文件(111行:JAVA_OPTS=“-Xms512m -Xmx1024m”) -
jar包部署在启动参数设置
java -Xms512m -Xmx1024m -jar xxx.jar
JVM调优参数有那些
- 设置堆空间的大小
-Xms堆初始化大小 -Xmx堆最大大小
默认是物理内存的1/4
为防止垃圾收集器在初始大小、最大大小之间收缩堆而产生额外的时间,通常设置成相同的值 - 虚拟机栈的设置
每个线程默认会开启1M的内存,用于存放栈帧、调用参数、局部变量等,工作中512k就够用
-Xss 对每个Stack大小的调整 - 年轻代中Eden区和两个Survior区的大小比例
-XXSurvivorRation=8 #表示年轻代中分配比率:survivor:eden=2:8
默认是8:1:1;
通过增大eden区的大小,来减少YGC发生的次数,但在eden区满的时候,由于占用的空间较大,导致释放缓慢,STW时间较长的,所以一般是不确定的,需要根据程序情况调优 - 年轻代晋升老年代阈值
-XX:MaxTenuringThreshold=threshoid
默认:15 (取值范围:0~15) - 设置垃圾回收收集器
默认是并发的垃圾回收器
-XX:+UseParallelGC
-XX:+UseParallelOldGC
设置垃圾回收期
-XX:+UseG1GC
JVM调优的工具
命令常用的是:
-
jps 查询进程
-
jstack 查看java进程内线程的堆栈信息
-
jmap 查看堆转信息
jmap -heap 进程id
jmap -dump:format=b,file=指定路径/xxx.hprof 进程id (生成堆转信息dump文件)
-
jhat 堆转储快照分析工具
-
jstat JVM统计监测工具,用来显示垃圾回收信息、类加载信息、新生代统计信息等
使用可视化工具查看:VisualVM
Java内存泄漏如何排查
出现内存泄漏的有三个地方
- JVM stacks虚拟机栈 StackOverFlowError
- 方法区/元空间 OOM:Metaspace
- Heap 堆 OOM:java heap space
内存泄漏和内存溢出都涉及到Java虚拟机(JVM)中的内存管理,但它们是不同的概念。
内存泄漏(Memory Leak):
内存泄漏是指程序中分配的内存空间在不再需要时没有被释放,导致该内存无法被重新使用,最终耗尽可用内存。
典型的内存泄漏场景包括对象被持续引用而无法被垃圾回收器回收、缓存对象无限增长、长时间存在的监听器未被移除等。
内存溢出(Memory Overflow):
内存溢出是指程序在申请内存空间时,无法获得足够的内存而导致无法继续执行。
典型的内存溢出情况包括堆内存溢出(Heap Overflow)和栈内存溢出(Stack Overflow)。堆内存溢出通常是由于创建了过多的对象而耗尽了堆内存,而栈内存溢出则是由于递归调用或者方法调用链过长而耗尽了栈内存。
1.通过jmap指令获取堆内存快照dump
2.VisualVM分析dump文件
3.通过查看堆转储信息的情况,定位内存溢出问题
CPU飙高排查方案
1.使用top命令查看占用CPU的情况,定位到那个进程占用CPU较高
2.使用ps命令查看进程中的线程信息
3.使用jstack命令查看进程中那些线程出现问题了,最后定位原代码