浅入理解JVM虚拟机
1. 类加载过程
验证 —— 准备 —— 解析 —— 初始化
验证阶段: 判断.class文件符合规范标准
准备阶段:给类以及静态变量分配内存并给初始值 “0”
解析阶段: 维护哥哥字段,方法类的内存指针或偏移量
初始化阶段: 变量赋值(真实的值),执行代码
什么时候一个类会进行初始化阶段?
1.new一个实例化对象时
2.包含main方法的主类
3.需要初始化子类时,必须先初始化父类
2. Tomcat如何实现热部署?
类加载器与双亲委派机制
启动类加载器 —— 扩展类加载器 —— 应用类加载器 —— 自定义类加载器
双亲委派机制也就是拿到一个类,先交给他的父类加载器加载,一层一层传递,如果都加载不了再回到自己的加载器加载,
这样就保证了每个类都会尽可能的在最上层加载器加载,且只会加载一次。保证了沙箱安全
Tomcat的实现方式
第一种,在WEB-INFO/class 目录下的文件,使用WebApp类加载器
启用一个线程来检测class文件的modify时间是否发生变化,如果发生变化就时1开关reloadable = true,使整个容器重启,全部重新加载
第二种,jsp类文件使用Jsp类加载器加载,Jsp类加载器属于一次性的
当jsp文件每次被修改时,都会使用一个新的Jsp类加载器加载。每此都会有新的jsp加载到jvm,并丢弃上一个
3. 垃圾回收器与垃圾回收算法
1.复制算法,用于新生代,Eden区与s1区中存活对象复制到s2区,剩下对象干掉
优点:不会产生内存碎片 缺点: 始终浪费一块空间
2.标记清除算法,用于老年代,先标记出哪些是垃圾,再统一清理
优点:节省空间 缺点:容易产生内存碎片
3.标记整理算法,用于老年代,就是在标记清除的基础上加一次整理,把内存碎片干掉
缺点:耗时
ParNew垃圾回收器,用于新生代,支持多线程,STW
CMS垃圾回收器,用于老年代,四个阶段 初始标记(标记GCRoot直接引用对象) —— 并发标记(GCRoot间接引用对象) —— 再次标记(补充并发标记中创建的对象) —— 并发清除
(GCRoot直接引用对象包括静态变量,局部变量所引用的对象。 间接引用对象指的是,其中被引用的对象的全局变量对象这种)
其中初始标记和再次标记是STW的,也占用的时间比较短
CMS采用标记清除,可配置JVM参数,多少次标记清除后一次标记整理
4. 关于GCRoot
用作可达性分析的根,一般是类静态变量和栈中的局部变量的对象
finalize()方法,
GC前会调用该对象的finallize()方法,试图抢救一下。所以可以在该方法中将当前对象挂上GCRoot链路,避免被回收
finalize 方法只会被执行一次,如果第一次执行 finalize 方法此对象变成了可达确实不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被回收!
5. 关于方法区
方法区属于概念,jdk1.8之前是永久代实现(与堆内存连续的),之后是元空间实现(在JVM外的本地内存)
方法区存的是类的模板,动态生成的类,静态变量,常量池(注意1.8以后常量池物理上存在于堆内存,但是逻辑上还是属于方法区的,咱也不懂)
方法区发生回收需要满足的条件
1.该类的所有实例对象都已经被JVM回收
2.加载该类的类加载器被回收
3.该类的Class对象没有任何引用
(个人感觉这些条件如此苛刻,因此方法区溢出导致的FullGC大概率不起什么作用,方法区回收不掉,从而导致无限的FullGC)
6. 对象进入老年代的几种情况
1.在年轻代反复存活15次,正常进入老年代 (在默认JVM参数配置下)
2.大对象从创建直接进入到老年代
3.一次YoungGC后存活的对象太多导致S2区装不下,会有部分对象直接进入老年代而不是全部对象
4.动态年龄判断规则,YoungGC后 年龄1 + 年龄2 + 年龄3 + ... 年龄n的对象总和大于S区的50%,则年龄最大的对象进入到老年代
7. 触发OldGC的几种情况
1.最正常情况,老年代空间不足,占用超过92%,直接触发OldGC
2.在YoungGC后,晋升到老年代的对象大于老年代剩余空间,触发OldGC
3.老年代空间担保机制,
在YoungGC之前,先判断老年代剩余空间是否小于平均每次YoungGC的大小。 先触发一次OldGC 再进行YoungGC
8. 出现OOM的几种情况
堆内存溢出:
1.高并发,请求量大,或者计算复杂,导致大量对象存活且GC干不掉
2.系统内存漏洞,导致大量对象内存泄漏(如经典八股文ThreadLocal)
方法区溢出:
1.给Metespace的空间太小了 (一般不会出现,除非人为的设置太小)
2.cglib等动态生成的类太多造成溢出
栈空间溢出:
1.一般是代码问题,无限递归,导致不断地方法栈入栈
9. GC调优的思路
一句话:尽量让对象在新生代分配和回收而不进入老年代,也就减少FullGC的影响
第一类,少让对象进入老年代
1.少用大对象
2.适当提高晋升年龄阈值
3.适当提高年轻代大小及S区大小
4.尽量让对象快点死,朝生夕死,不要拖着不死导致干不掉,最后拖到老年代了
第二类,减少YoungGC次数
1.少创建无用对象
第三类,减少FullGC次数
1. 少让对象进入老年代,也就少触发FullGC (好像废话)
2. 适当调大老年代的空间
3.避免动态创建太多类导致方法区打满,从而导致FullGC
4.避免干不掉的对象,如内存泄漏导致频繁FullGC
5.FullGC后适当进行标记整理,减少内存碎片
10. GC调优工具
GC日志
1.YoungGC日志
2.FullGC日志
jstat
1. 查看JVM Eden,Surivor区以及老年代内存的使用情况
2. 查看YoungGC,FullGC的执行次数和耗时,触发频率
3.查看新生代对象增长速率,老年代对象增长速率
jmap
1.查看Eden区,Surivor区,以及老年代内存使用占比
2.查看内存中对象占用情况,具体到哪个包下的哪个类,占了多大
3.dump下此时的内存快照,利用分析工具分析