一、类加载器
类装载器:
Java运行时负责动态加载Java类到Java虚拟机内存空间中的环境部分
类加载器有哪几种:
启动类加载器、扩展类加载器、应用程序类加载器、用户自定义类加载器(用户自定义继承ClassLoader抽象类的类)
双亲委派机制:
用户定义的类,受限往上捅,从jdk自带的jar进行加载,找不到,才一层一层往下找
沙箱安全机制:
用户自定义的类必须和jdk自带的类定义不同。比如自定义一个java.lang.String 此时双亲委派机制会先从启动类加载器加载,结果JDK自带也有java.lanng.String 因此就不会再使用用户自定义的类,此时就会报错找不到main方法。因为jdk原生的java.lang.String没有main方法
因此用户自定义的类不能污染JDK原生自带的类!!!
二、执行引擎:
执行引擎的任务就是把字节码文件编译成操作系统可识别的的本地机器指令
三、本地方法栈、Java方法栈
带native的方法,没有具体实现。
用于调外部接口。比如线程里的start0()方法,实际上就是调用C语言的方法。带有native的本地方法栈里的本地方法实际上就是java的尽头
Java方法栈主要运行java的方法,按照程序的运行顺序执行java的方法
四、程序计数器
即PC寄存器所在位置,用于记录下一行方法执行的行数,本质就是个指针,类似于排版记录表一样的存在
五、方法区
编辑
方法区实际上就是存储了类的结构信息,换言之就是存了类的模板(大Class)
理解:
方法区 f = new 永久代()
方法区 f2 = new 元空间()
六、堆
堆和栈是jvm最重要的两块内容!!!!!!
ps:栈管运行、堆管存储
OOM流程:
1、java新建的对象都存储在伊甸园区
2、当伊甸园区满了以后开启YGC(轻GC),如果存在存活对象即还需要使用的对象则复制并存于幸存者0区(也称from区)
3、下一次YGC时,将幸存者0区与幸存者1区交换,此时from和to区(幸存者1区)互换
4、(默认15次)反复YGC以后,超过15次的对象则复制存于老年区
5、若老年区也满了,则开启FULLGC(FGC)
6、若反复FGC以后,依然存在老年区空间不足,则抛出OOM(OutOfMemmoryError错误)
PS:
1、新生代、老年代比例:1:2
2、新生代中,伊甸园区:from:to = 8:1:1
堆参数调整:
-Xms:初始堆内存大小
-Xmx:峰值堆内存大小
以上两者最好一致,原因:
1、避免GC和应用程序争抢内存
2、避免jvm内存理论值和峰值忽高忽低
GC日志信息分析:
FullGC日志分析:
PS:FullGC通常比GC慢十倍以上
慢的原因是:养老区比新生区大太多,所以全空间扫描的范围大很多!!!
一、引用计数法
ps:根本不用!!!因为存在循环引用,所以局限性很明显。
System.gc()
以上命令可以手动唤起GC!!!
二、复制算法
PS:java8定死了MaxTenuringThreshold最大不能超过15
优点:不产生内存碎片!!!
缺点:占用空间!因为会同时用到两倍相同的空间!
算法原理图:
三、标记清除算法
相当于是时间换空间!
PS:标记的方式:判断对象==null进行标记
四、标记压缩(也叫标记整理)算法
PS: 为什么老年代使用标记清除-标记整理算法(明明会特别耗时)?
因为老年代很少发生GC,因此使用如上两种算法整合是为了保证垃圾回收的合理性
五、实际讨论四种算法
实际情况其实都是用标记清除-标记整理结合(可以当作第五种算法,但理论中并不存在)
问:哪种算法最好?
答:没有最好的算法,只有根据每一代进行对应的垃圾回收算法才能使效率最高!
六、实际上现已存在最好的垃圾回收算法:G1垃圾回收算法(但只存在于java9以后的地方,目前不涉及)
*JMM内存模型(Java Memory Model)*
JMM三大特性:
1、可见性(各个线程相互可见)
2、原子性(不可分割)
3、有序性(按照汇编顺序顺序执行)