1.JVM:
-
jvm:jvm是java的运行环境(java二进制字节码的运行环境)
-
好处:一次编写到处运行
自动内存管理,垃圾回收机制
2.程序计数器:
-
线程私有的,每个线程一份,内部保存的字节码的行号,用于记录正在执行的字节码文件指令的地址
3.堆:
-
线程共享区域,主要保存对象实例,数组等,当堆内存中没有空间可分配给实例,也无王法在扩展时,就会抛出OutOfMemory Error异常
-
堆中分为年轻代和老年代2个区域,年轻代被划分为3个区域,Eden区和2个大小严格相等的Survivor区,在每次垃圾回收时,Eden区中存活的对象会被移到其中一个Survivor区。Survivor区用于存放从Eden区复制过来的存活对象,根据JVM的策略,在经过几次垃圾回收集后,仍然存活于Survivor的对象将被移动到老年区间
老年区主要保存生命周期长的对象,一般是一些老的对象
-
jdk1.7与jdk1.8的区别:在jdk1.8后,方法区(元空间,永久代)移出堆将数据存储在本地内存里面,因为随着动态类的加载,方法区内存会变得不可控,容易导致堆内存不足,方法区主要存在变量,静态变量,类信息,编译后的代码
4.虚拟机栈:
-
每个线程运行的所需要的内存,称为虚拟机栈,每个栈有多个栈帧组成,每个栈帧对应相应着每次的方法调用时所占用的内存,
每个线程只有一个活动栈帧,对应正在执行的那个方法
-
垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放
-
栈内存分配空间不是越大越好,还要看具体业务实现,默认的栈内存通常为1024k,栈帧过大会导致线程数变少
-
如果方法内局部变量没有逃离方法的作用范围,他是线程安全的,如果局部变量引用了对象,并且逃离的方法的作用范围,需要考虑线程安全
-
栈帧过多导致内存溢出,典型问题1:递归,栈帧过大导致栈内存溢出
5.方法区:
-
方法区是各个线程所共享的存储区域
-
方法区存储类的信息,运行时常量池,存储在本地内存中
-
在虚拟机启动时创建,关闭后销毁
6.运行时常量池:
-
常量池可以看成一张表,虚拟机的运行指令根据这张表常量找到要执行的类名,方法名,参数类型,字面量
-
当类被加载时,他的常量池信息就会被放到运行时的常量池,并把里面的符号引用改为真实引用
7.直接内存:
-
直接内存并不属于JVM中的内存结构,不由JVM进行管理,是虚拟机的系统内存
-
常见于NOI的操作,用于数据的缓存区域,分配的成本较高,但读写性能较高,不受JVM的内存管理
类加载器:用于装载字节码文件(.class文件)
运行时数据区:用于分配存储空间
执行引擎:执行字节码文件或本地方法
垃圾回收器:用于对JVM中的垃圾内容进行回收
8.类加载器:
-
jvm只会运行二进制文件,类加载器会将字节码文件加载到jvm中,从而使java程序能够运行起来
-
类加载器根据各自加载范围的不同,划分为四种类加载器:
启动类加载器(BootStrap ClassLoader):
该类并不继承ClassLoader类,其是由C++编写实现。用于加载
** JAVA_HOME/jre/lib**目录下的类库。
扩展类加载器(ExtClassLoader):
该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。
应用类加载器(AppClassLoader):
该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。
自定义类加载器:
开发者自定义类继承ClassLoader,实现自定义类加载规则。
9.双亲委派模型:
-
加载一个类先委托上一级加载器进行加载,如果委托的也有上一级则继续委托,如果上级没有被加载出来,子加载器则尝试加
-
通过双亲委派可以防止某一个类被重复加载,当父类加载后则无需继续加载,保证唯一性
-
为了安全保证核心类库不能被篡改
10.类装载过程:
-
加载:查找和导入字节码文件
-
验证:确保class文件的准确性
-
准备:为类变量分配空间,并赋予初始值
-
解析:把类中的符号引用转化为直接验证
-
初始化:对类的静态变量,静态代码块执行初始化操作
-
使用:JVM开始从入口方法开始执行用户的程序代码
-
卸载:当用户程序代码被执行完毕后,JVM边开始销毁创建的对象
11.对象什么时候可以被回收:
-
如果一个对象或者多个对象没有任何的引用执向他,那么这个对象就行垃圾,如果定位了垃圾,就可能被垃圾回收机制回收
定位垃圾的方法有两种:可达性分析,引用计数器
-
引用计数器:一个对象被引用了一次,在当前的对象头上递增一次引用次数,如果这个对象的
引用次数为0,代表这个对象可回收,当对象间出现了循环引用的话,则引用计数法就会失效
-
可达性分析算法:会存在一个根节点【GC Roots】,引出它下面指向的下一个节点,再以下一个
节点节点开始找出它下面的节点,依次往下类推。直到所有的节点全部遍历完毕。
根对象是那些肯定不能当做垃圾回收的对象,就可以当做根对象
12..jvm的垃圾回收算法:
-
标记清除算法:步骤分为两步:标记和清除,首先使用可达分析算法得出的垃圾进行标记,然后对这些标记为可回收的垃圾进行回收
效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要
停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
(重要)通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收
的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。
-
标记整理算法:在标记时跟标记清楚算法差不多,但清除时会把所有存活的对象都移向一端,清除边界以外的对象,解决了碎片化问题,算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。与复制算法对比:复制算法标记完就复制,但标记整理算法得等把所有存活对象都标记完毕,再进行整理
-
复制算法:复制算法是把内存分为同等大小的两块空间,每次就使用其中的一块,在进行垃圾回收时,会把所有存活的对象,复制到另一块空间上面,然后将该空间进行清空,如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合
在垃圾对象多的情况下,效率较高
清理后,内存无碎片:
分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低
-
分代回收算法:对堆内存进行年龄划分,对不同的区域使用合适的垃圾回收机制
13.,jvm的分代回收:
-
在jdk1.8时,队3被分成2块空间,新生代和老年代(内存比为1:2),对于新生代,内部又被分为了三个区域。Eden区,from区,to区(8:1:1),
-
回收策略:新创建的对象,都会先分配到eden区
当伊甸园内存不足,标记伊甸园与 from(现阶段没有)的存活对象
将存活对象采用复制算法复制到 to 中,复制完毕后,伊甸园和 from 内存都得到释放
经过一段时间后伊甸园的内存又出现不足,标记eden区域to区存活的对象,将存活的对象复制到from区
当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)
-
MinorGC【young GC】发生在新生代的垃圾回收,暂停时间短(STW)
Mixed GC 新生代 + 老年代部分区域的垃圾回收,G1 收集器特有
FullGC: 新生代 + 老年代完整垃圾回收,暂停时间长(STW),应尽力避免
14.jvm有哪些垃圾回收器:
-
串行垃圾收集器:垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成
-
并行垃圾收集器 :垃圾回收时,多个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成
-
CMS(并发)垃圾收集器:相对较旧的并发垃圾收集器,适用于需要减少停顿时间的应用。分为初始标记、并发标记、重新标记和并发清理四个阶段。
15.G1垃圾回收器:
-
应用于新生代和老年代,在**JDK9之后默认使用G1**
-
划分成多个区域,每个区域都可以充当 eden,survivor,old, humongous,
-
其中 humongous 专为大对象准备
-
采用复制算法
-
响应时间与吞吐量兼顾
-
分成三个阶段:新生代回收、并发标记、混合收集
-
如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC
16.java的内存泄露排查顺序:
-
通过jmap指定打印他的内存快照 dump
-
通过工具, VisualVM(Ecplise MAT)去分析 dump文件
VisualVM可以加载离线的dump文件
-
通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题
17.cpu飙高和排查
-
使用top命令查看占用cpu的情况
-
通过top命令查看后,可以查看是哪一个进程占用cpu较高,上图所示的进程
为:30978
-
查看当前线程中的进程信息
-
可以根据线程 id 找到有问题的线程,进一步定位到问题代码的源码行号
执行命令