一、什么是JVM
JVM 全称是Java Virtual Machine ,Java 虚拟机。
所有的Java程序都在Java虚拟机中运行,存在于内存中。
二、执行过程
1、装载 :二进制字节码并加载至JVM中
2、链接:字节码校验,解析接口、类。初始化静态变量赋值。校验属性、方法的存在
3、初始化:静态代码块、构造函数、静态属性
三、JVM内存模型
寄存器:PC寄存器是用于存储每个线程下一步将执行的JVM指令
方法区:类信息(名称、修饰符等),类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息。方法区域也是全局共享的。
本地方法堆栈:此区域用于存储每个native方法调用的状态。
JVM栈:线程私有的,基本类型(int,double)、对象地址(指向堆),每个方法就是一个栈帧。
一个Native Method就是一个Java调用非Java代码的接口。一个Native Method是这样一个Java的方法:该方法的实现由非Java语言实现,比如C或C++。
四、类加载机制
双亲委派机制。类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,
如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
五、堆内存分析
JVM将内存划分为:
New(年轻代)
Tenured(年老代)
永久代(Perm)
六、垃圾回收机制
1、引用计数(reference counting)
原理:此对象有一个引用,则+1;删除一个引用,则-1。只用收集计数为0的对象。
2、复制
原理:把内存空间划分为2个相等的区域,每次只使用一个区域。垃圾回收时,遍历当前使用区域,把正在使用的对象复制到另外一个区域。
优点:不会出现碎片问题。
缺点:1、暂停整个应用。2、需要2倍的内存空间。
3、标记-清扫
原理:对于“活”的对象,一定可以追溯到其存活在堆栈、静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。第一阶段:从GC roots开始遍历 所有的引用,对有活的对象进行标记。第二阶段:对堆进行遍历,把未标记的对象进行清除。这个解决了 循环引用的问题。
缺点:1、暂停整个应用;2、会产生内存碎片。
4、标记-压缩
原理:第一阶段标记活的对象,第二阶段把为标记的对象压缩到堆的其中一块,按顺序放。
优点:1、避免标记扫描的碎片问题;2、避免停止复制的空间问题。
5、分代
原理:基于对象生命周期分析得出的垃圾回收算法。把对象分为年轻代、年老代、持久代,对不同的生命周期使用不同的算法(2-3方法中的一个即4自适应)进行回收。
(1) minor gc
(2)full gc
1、Full GC将会同时回收年轻代、年老代
2、当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载
七、内存调优
系统崩溃前的一些现象:
1、每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s
2、FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
3、年老代的内存越来越大并且每次FullGC后年老代没有内存被释放
Q:为什么崩溃前垃圾回收的时间越来越长?
A:根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判 断内存泄漏的依据
Q:为什么Full GC的次数越来越多?
A:因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收
Q:为什么年老代占用的内存越来越大?
A:因为年轻代的内存无法被回收,越来越多地被Copy到年老代
内存调优原则:
1、多数的Java应用不需要在服务器上进行GC优化;
2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);
4、减少创建对象的数量;
5、减少使用全局变量和大对象;
6、GC优化是到最后不得已才采用的手段;
7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;
GC优化的目的:
1、将转移到老年代的对象数量降低到最小;
2、减少full GC的执行时间;
为了达到上面的目的,一般地,你需要做的事情有:
1、减少使用全局变量和大对象;
2、调整新生代的大小到最合适;
3、设置老年代的大小为最合适;
4、选择合适的GC收集器;