图解:
一、类的加载classLoader
1、类的主动加载方式(4种,经常被问到):
1>构造函数:eg:Student student = new Student();
2>反射、Clone;
3>初始化子类的时候父类会被初始化;
4>调用一个静态方法。
2、类加载的过程:
1>加载 Loader
通过类的全路径名,获取类的二进制数据;
解析流,将类的信息存放于方法区;
创建class类的实例。
2>验证
验证加载字节码是否合法。
3>准备
虚拟机为这个类分配内存空间
4>解析
将符号引用转化为直接引用。关于二者的概念区别可参考:https://www.cnblogs.com/shinubi/articles/6116993.html
5>初始化
此时,类已经被顺利加载到系统中,类开始执行java字节码。
二、JMM(Java Memory Manager)
1、 程序计数器
有一个比较小的内存空间;
正常的非native方法:字节码的行号指示器,native方法为空;
内存私有;
唯一的一个没有规定任何OOM情况的区域。
2、本地方法栈
为native方法服务。
3、虚拟机栈
私有的;
描述方法执行的内存模型(栈帧);
方法的执行到结束对应栈帧的入栈和出栈。
4、方法区/永久代/元空间
共有的。
方法区:存放类的信息、常量池、方法数据、代码等,是个逻辑上的概念。
永久代(JDK7及之前):内存中不会被GC的一块永久区域;加载了类信息和元数据信息(关于元数据可参考https://blog.csdn.net/CSDN4006600/article/details/100268792);如果空间被加载的类信息占满,就会发生OOM异常。
元空间(JDK8及之后):永久代因为可能产生OOM被元空间取代,元空间直接使用了本地内存;虚拟机整合,合二为一HopSpot、JRockit(原来没有永久代)。
5、堆
共有的;
创建的对象或者数组保存的地方;
GC发生的地方;
新生代(eden、s0、s1)和老年代。
三、标记一个垃圾对象
可触及性:确定一个对象是不是可以被回收;
从根节点开始,访问某个对象,如果能访问到,说明这个对象是可用的,反之,是不可用的;
可触及-->可复活(标记为垃圾对象时会调用一次finalize方法,给对象唯一一次复活的机会)-->不可触及。
public class DieAliveObject {
private static DieAliveObject dieAliveObject;
public static void main(String[] args) {
dieAliveObject = new DieAliveObject();
for (int i = 0; i <= 1; i++) {
System.out.println(String.format("----------GC nums=%d----------", i));
dieAliveObject = null; // 被标记
System.gc(); // 通知JVM可以进行回收
try {
Thread.sleep(100); // 等待GC执行
} catch (InterruptedException e) {
e.printStackTrace();
}
if (dieAliveObject == null) {
System.out.println("dieAliveObject is null");
} else {
System.out.println("dieAliveObject is not null");
}
}
}
/**
** 仅调用一次,给对象一次复活机会
*
** @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
System.out.println("finalize is calledѺ");
super.finalize();
dieAliveObject = this; // ֵ对象复生,添加引用
}
}
四、引用级别
1、强引用:程序中的引用;
2、软引用:堆空间不足时才会被回收。
3、弱引用:发生GC时,只要发现弱引用就会被回收;
4、虚引用:与没有引用一样。
五、槽位复用
可参考https://blog.csdn.net/weixin_38106322/article/details/108063173
六、对象的分配
1、栈上分配
1>逃逸分析(变量或对象没在方法区内)
2>标量替换
标量:不可进一步分解的量(int、long等);
聚合量:可以被进一步分解的量。
3>替换
通过逃逸分析,确定该对象会不会被外界访问;用标量去替代聚合量。
2、TLAB分配
TLAB:Thread Local Allocation buffer,线程本地分配的缓冲区;
作用:自己使用自己的缓冲区,避免多线程之间的获取控件的冲突;
因为仅为缓冲区,所以内存空间比较小,没办法分配大的对象。
3、堆分配:绝大多数对象的分配方式。
七、常用的垃圾回收算法
偏向策略:【分代算法;分区算法】
八、垃圾回收器
1、串行回收器--serial古老
特点:只使用单线程进行GC,独占式GC;单cpu情况下效率高。
2、并行回收器P a r N e w & P a r a l l e l G C & P a r a l l e l O l d G C
将串行回收器多线程化;
与串行回收器有相同的回收策略、算法、参数。
3、G1:garbage first garbage collector
优先回收垃圾比例最高的区域,将堆划分为多个区域,每次收集部分区域来减少GC产生的停滞时间,作用域全域。
初始标记根节点,初始标记和跟区域扫描针对新生代,直接标记可以去老年代的对象,并发标记即标记存活对象。重新标记:停止所有应用程序,最后的标记阶段,避免前面在应用程序跑的时候遗漏的。独占清理:计算存活比例,获取垃圾最多的区域。
混合收集:对占比较高的 进行清理。
九、JVM常用参数
十、JVM监控优化
LINUX top命令
LINUX vmstat命令
LINUX Iostat命令
J D K 工 具 j p s
J D K 工 具 j s t a t
J D K 工 具 j i n f o
J D K 工 具 j m a p
J D K 工 具 j h a t
J D K 工 具 j s t a c k
J D K 工 具 j c m d
如有描述不准确的,欢迎指正,互相学习!