JVM
类加载过程
类加载过程分为5各阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)。
加载
将.class文件从磁盘加载到内存
类加载方式:隐式加载指程序在使用new关键字创建对象时,会隐式的调用类的加载器把对应的类加载到JVM中。显示加载指通过直接调用class.forName()方法把所需的类加载到JVM中。
验证、准备、解析---连接
验证
验证.class文件的正确性,包含:文件格式验证、元数据验证、字节码验证、符号引用验证
准备
为静态变量分配内存,初始化默认值,不进行赋值(常量除外,常量初始化同时赋值,后续值不能再改变)
解析
JVM将常量池中的符号引用替换为直接引用的过程;
符号引用:以一组符号引用描述所引用的目标,可以理解为,以文字形式来描述引用关系;
直接引用:可以理解为,直接指向内存中的地址。
初始化
执行类构造器<clinit>()方法的过程。对类的静态变量、静态代码块执行初始化操作。
双亲委派机制
当一个类加载器收到类加载的请求时,它不会直接加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类时,才会由当前这个加载器来负责类的加载。(一层一层抛上去,直到Bootstrap ClassLoader判断是否可以加载,若不能,再一层层抛下去,若最终不能被加载抛出classNotFoundException)
Java中提供四种加载器,每一种加载器都有指定的加载对象:
Bootstrap ClassLoader(启动类加载器):主要负责加载Java核心类库,%JAVA_HOME%\lib下的rt.jar,charsets.jar,resources.jar和class等;
Extension ClassLoader(扩展类加载器):主要负责加载目录%JAVA_HOME%\lib\ext下的jar包和class文件;
Application ClassLoader(应用程序类加载器):主要负责加载当前应用的classpath下的所有类;
User ClassLoader(用户自定义类加载器):可加载指定路径的class文件;
意义:
- 避免类的重复加载,当父加载器已经加载过某一个类时,子加载器不会再重新加载这个类
- 保证安全性(防止核心类库被篡改),每一种加载器都有指定的加载对象
内存结构
类装载子系统
将所有.class文件的数据加载到元空间,以方便运行调用;(加载类信息)
程序执行引擎
执行代码,并会将执行到的代码的行号记录到程序计数器中
RuntimeDataArea 运行时数据区
堆:对象、成员变量、字符串常量池,采用分代管理算法
栈:也叫做虚拟机栈、线程栈,为每个线程都分配一块区域,在该线程内部调用方法时,会在该区域内为方法开辟一块栈帧,方法内的局部变量都保存在该栈帧中;
栈帧:局部变量表 操作数栈 动态链接 方法出口
动态链接:保存当前方法在元空间上存储地址;
方法出口:保存当前方法结束后返回原来方法的行号;
元空间(方法区):从JDK1.8开始,将数据直接保存在物理磁盘上;类信息、静态变量、静态方法、运行时常量池(就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息,它是.class文件中的,当该类被加载,他的常量池信息就会放入运行时常量池,并把里面的符号地址变成真实地址)
本地方法栈:服务于本地方法,本地方法内部所有局部变量都保存在本地方法栈中,其内部结构与栈相同,都是为调用的方法分配栈帧区域,该方法内部的局部变量保存在这个栈帧中;
程序计数器:用于保存当前线程所正在执行的字节码指令的行号。
垃圾回收算法
标记-清除算法
定义:将存活的对象进行标记,未标记的进行清除;
缺点:碎片化空间,内存利用率低;
复制算法 minor gc使用
定义:将可用内存分为两块,每次指利用其中一半内存,当一半内存不足时,将可用对象复制到另一半内存,并将之前一半清空;
缺点:可用内存减少;
标记-整理算法
定义:将可用对象进行标记,将未标记对象清除后,将可用对象进行整理,将可利用内存整理到一起,实现内存连续;
缺点:效率较低。
堆:分代管理思想
新生代(young generation):老年代(old generation)=1:2
Eden(伊甸园区) | survivor0-s0 | survivor1-s1 | 老年代 1、创建时太大的对象会直接保存到老年代中 2、从年轻代产生的年代数达到15的对象 |
Eden:s0:s1 = 8:1:1
年轻代工作原理
新创建的对象会保存在Eden区,当Eden区内存不足会对新生代触发一次minor gc,在回收之前,会使用可达性分析算法,从栈开始,标记所有正在使用的对象,将使用的对象复制到一个survivor区中,进行年代数+1操作,再清空Eden区和另一个survivor区;当对象年代数达到15,会移入老年代中。
老年代工作原理
年老代中保存大对象和年代数达到15的对象,当年老代内存不足时,会对整个堆内存进行full gc,触发STW(Stop The World)状态,用户体验非常差,此时需要进行JVM调优。
minor gc和full gc都会触发STW(Stop The World)状态,但minor gc回收内存小,所以造成的卡顿几乎感觉不到,所以minor gc的触发频率可以高,但full gc是对整个堆内存进行回收,照成的卡顿很明显,所以要尽量减少full gc的触发。
JVM优化
- 尽可能减少大对象的产生--操作文件时,尽可能多次操作
- 尽可能让对象在新生代回收走--扩大堆内存大小、调整Eden和survivor比例、调整新生代和老年代比例(慎用)
- 堆内存大小在项目部署时配置文件中调整,idea安装目录下 bin->idea.exe.vmoptioins
-server
-Xms 堆初始大小 -Xmx 最大堆内存
-XX:+UseConcMarkSweepGC 指定GC
-XX:NewSize=n 设置新生代大小
-XX:MaxPermSize=n 设置永久代大小(永久代即元空间)
-XX:NewRation=n 新生代和老年代的比值 为3 表示年轻代:老年代=1:3
-XX:SurvivorRation=n Eden和两个Survivor区的比值 为3 表示 Eden:Survivor=3:2
GC分类
SerialGC 单线程
ParalleGC 并行情况作用GC
ConcMarkSweepGC 并发情况作用的GC 从JDK1.5开始默认提供
G1GC 从JDK1.9开始默认提供
JDK1.9堆内存结构发生改变,使用块结构,但仍保留分代思想
将堆内存分为很多块
G1GC回收机制:
有一个后台线程会记录当前回收利用率最高的块,将其最为最优先回收的块,当内存不足时,对该块进行回收,若回收够用,则不再继续回收,大大降低STW时间;且每个块在下次使用时可以作为不同的的区使用。
栈内存溢出(StackOverflowError)
当系统进行方法调用时,会在栈空间中分配一块栈帧,当栈帧深度超过栈的深度时,就会产生栈内存溢出。若方法递归调用深度过大,则可能产生栈内存溢出。
堆内存溢出(OutOfMemoryError)
创建新对象时,堆内存中空间不足。对象过多且得不到及时回收;创建线程数超过了操作系统的限制;大量的类信息存储与永久代(元空间);超过98%的时间都在用来做GC并且回收了不到2%的堆内存。
4种引用
强引用
强引用:最普遍的引用,如果一个对象具有强引用,那垃圾回收器宁愿抛出OOM(OutOfMemoryError)异常也不回收它;
强可达:如果一个对象与GC Roots之间存在强引用,则称这个对象为强可达对象;
如:声明一个变量指向一个实例时,就是在创造一个强引用,当我们不再使用某个对象时,可以将相应的引用设置为null,消除强引用以防止堆内存溢出。
软引用
软引用:使用SoftRefence创建的引用,强度弱于强引用,被引用对象在内存不足时被回收,不会产生内存溢出;
在垃圾回收器没有回收它的时候,软可达对象就像强可达对象一样,可以被程序正常访问和使用,但是需要通过软引用对象间接访问,需要的话也能重新使用强引用将其关联。所以软引用适合用来做内存敏感的高速缓存。
弱引用
弱引用:使用WeakReference创建的引用,用来描述非必需对象,比软引用更弱。在发生GC时,只要发现弱引用,不管系统堆内存是否足够,都会对持有弱引用的对象进行回收;
弱引用一般和一个引用队列联合使用,一旦一个弱引用对象被垃圾回收器回收,便会加入到一个引用队列。
软引用和弱引用都用来描述非必需对象,那么什么时候用SoftReference,什么时候用WeakReference
如果缓存的对象比较大,使用频率相对较高,那么使用SoftReference,这样可以让缓存对象有更长的生命周期。
如果缓存的对象比较小,使用频率一般或相对较低,那么使用WeakReference更好。
一般来说,我们很少直接使用WeakReference,而是使用WeakHashMap。在WeakHashMap中,内部有一个引用队列,插入的元素会被包裹成WeakReference,并加入队列中。
虚引用
虚引用:使用PhantomReference创建的引用,是所有引用类型中最弱的一个。一个对象是否有虚引用存在,完全不会对其生命周期构成影响,页无法通过虚引用获得一个实例。
虚引用必须和引用队列一起使用。虚引用可以用于跟踪垃圾回收过程,在对象被GC回收时收到一个系统通知。当GC准备回收一个对象时,发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前,不会彻底销毁该对象。所以可以通过检查引用队列中是否有相应的虚引用来判断对象是否已经被回收。
引用类型 | 回收时间 | 用途 |
强引用 | 从来不会 | 对象的一般状态 |
软引用 | 内存不足时 | 缓存 |
弱引用 | 垃圾回收时 | 缓存 |
虚引用 | -- | -- |