一、 执行引擎
前端编译(.java --> .class)字节码 不等于 机器码,需要jvm将字节码加载到内存中,需要通过执行引擎将字节码 解释/编译成机器码 后端编译(.class --> 机器码)。
执行引擎机制:
- 解释器 :将字节码逐行解释执行
- JIT编译器(即时编译器):将字节码整体编译为机器码执行
1、为什么JVM执行引擎设计为半解释型,半编译型?
逐行解释执行效率低 JVM会针对使用效率较高的热点代码进行编译,并缓存起来,执行效率提高 但是编译是需要消耗时间的。 所以jvm刚刚启动后,可以先通过解释器,解释执行代码 之后在使用编译器编译执行,两种结合在一起
二、垃圾回收
1、概述
垃圾收集机制并不是java语言创的,但是又是java的招牌,java可以自动垃圾回收
2、垃圾回收:
回收哪些区域:频繁回收堆内存,较少回收方法区,栈(溢出),本地方法栈(溢出),程序计数器没有垃圾回收。
3、什么是垃圾?
while (true){
new Random().nextInt();
}
垃圾是指在运行程序中没有任何引用指向的对象,这个对象就是需要被回收的垃圾。
4、为什么需要GC?
(1)垃圾如果不及时清理,越积越多,可能会导致内存溢出。 (2)垃圾多了,内存碎片较多,例如数组,需要连续空间 (3)随着应用程序应付的业务越来越大,没有GC就不能保证应用程序的正常进行。
5、早期垃圾回收
早期是手动回收不被使用的对象,例如C++,java语言是自动垃圾收集的。
6、垃圾回收机制
自动内存管理:无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险。以更专心地专注于业务开发 自动收集的担忧:自动回收方便了程序员的开发,但是降低处理内存问题的能力。 自动虽好,但是还是应该了解并掌握一些相关内存管理知识。
7、 Java 堆是垃圾收集器的工作重点
从次数上讲: 频繁收集 Young 区 较少收集 Old 区 基本不收集元空间(方法区
三、内存溢出与内存泄漏
溢出:内存不够用了 泄漏:有些对象已经在程序不被使用了,但是垃圾回收机制并不能判定其为垃圾对象,不能将其回收掉,这样的对象越积越多,长久也会导致内存不够用。
eg: 与数据库连接完之后,需要关闭连接通道,但是没有关闭。 io读写完成后没有关闭
四、垃圾收集算法分为两大类
1、垃圾标记阶段算法
主要是来判定哪些对象已经不再被使用,标记为垃圾对象, 判定对象为垃圾的标准:不被任何引用所指向的对象。Object obj =new Object();
1、引用计数算法(在jvm中不被使用)
如果有一个引用指向此对象,那么计数器加1,如果没有引用指向,计数器为0,此时就判定位垃圾。
优点:方便使用,设计简洁 缺点:增加了计数器的存储空间,计数需要消耗时间。
- 导致了循环引用问题,好几个对象之间相互引用,但是没有其他引用指向他们,此时垃圾回收不能回收他们,但是也没有引用指向,这就造成了内存泄漏。
Object obj = new Object();
obj =null;
2.可达性分析算法 / 根搜索算法(这是java目前所使用的垃圾标记算法)
解决:循环引用问题 ,设计简单,运行高效,防止内存泄漏 思路: 从一些活跃引用(GCRoots根)开始,如果对象被根直接或间接引用,那么此对象不是垃圾,否则被标记为垃圾对象。
(1) 哪些引用被用来当做根;
虚拟机栈中引用的对象(方法中引用的对象) 本地方法栈中引用的对象 静态变量所引用的对象 常量引用指向的对象 被synchronized当做锁的对象 java虚拟机内部的引用
- 总结:栈中引用的(正在使用的)方法区,常量池中(生命周期较长的),被synchronized当做锁的对象。
(2)finalize() 方法机制
- final(关键字) finally(代码块) finalize(方法)是object类中的一个方法,在对象被最终回收之前调用,只调用一次。
java运行对象在销毁前去调用finalize(),去处理一些逻辑,一般不用(不建议用).不要显示的去调用finalize()方法,在里面写代码一定要慎重!!!
- 在finalize()时,可能会导致对象复活。
- finalize()由垃圾回收器调用,没有固定的时间。
- 一个糟糕的finalize() 会严重影响GC性能。比如finalize 是个死循环。
垃圾:不是立刻被回收,只是被标记,有了finalize()方法/机制的存在,我们的对象有可能起死回生。
对象状态: 可触及的:从根节点开始,可以到达这个对象。(没有被标记为垃圾) 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活。确定为垃圾了,但没有调用finalize()方法。 不可触及的:对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会内调用一次。
package com.ffyc.database.jvm.gc;
public class CanReliveObj {
public static CanReliveObj obj;
@Override
protected void finalize() throws Throwable {
System.out.println("调用当前类重写的finalize()方法");
obj = this;
}
public static void main(String[] args) {
try {
obj = new CanReliveObj();
obj = null;
System.gc();
System.out.println("第1次 gc");
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
System.out.println("第2次 gc");
obj = null;
System.gc();
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2、垃圾回收阶段算法
1.标记-清除算法
- 标记阶段:标记出从根可达的对象,标记的是被引用的对象。
- 清除阶段:此清除并非直接将垃圾对象清除掉,而是将垃圾对象的地址维护到一个空闲列表中。之后如果有新的对象产生,判断空闲列表中的对象空间能否存放的下新的对象,如果能放的下,那么就覆盖垃圾对象。
优点:简单、容易理解 缺点:效率低,会产生STW(在回收时,停止整个应用程序),会产生内存碎片。
2. 复制算法
将内存分为大小相等的两块,每次只使用其中的一块儿区域即可。当回收时,将不是垃圾的对象,复制到另一块内存中,排放整齐。然后将原来的内存块清空。减少内存碎片。
- 优点:运行高效,减少内存碎片
- 缺点:用到两倍的内存空间,对于G1垃圾回收器,将每个区域又可以拆分成更多的小区域。需要维护各区之间的关系。
一般在新生代中的幸存者0和幸存者1这两个区域使用复制算法。
3.标记压缩算法
- 背景:复制算法需要移动对象位置,移动的数量如果多的情况下,效率低。对于年轻代来讲还是不错的。对于老年代大量的对象是存活的,如果需要移动就比较麻烦效率低。
- 实现: 将存活对象标记处来,重新在本内存空间中排放位置。清除其他空间的垃圾对象。
3、标记-清除 和 标记-压缩对比
标记-清除是不移动对象,不会把垃圾对象清除掉(维护在一个空闲列表中) 标记-压缩是要移动对象的,要清除掉垃圾对象。
- 优点:
不会像标记-清除算法那样会产生内存碎片; 不会像复制算法那样需要两块内存空间; - 缺点:
效率相对低,对象位置移动后需要重新设置对象地址,也会有STW
4、分代/分区收集
由于对象的生命周期长短不同,将不同的对象存储在不同的区域;针对不同的区域进行分区收集,提高收集效率。
五、垃圾回收中的相关概念
1、System.gc()的理解
调用System.gc()方法,会触发Full GC(整堆收集),但是不一定调用后会立刻生效,因为垃圾回收是自动的,一般情况下,不要在项目中显示的去调用。
2、STOP THE WORLD
stop the world —> STW 在垃圾回收时,会导致整个应用程序停止。 在标记垃圾对象时,需要以某个时间节点上内存中的情况进行分析(拍照 快照),因为不进行停顿的话,内存中的对象不停的变化,导致分析结果不准确。停顿是不可避免的,优秀的垃圾回收器尽可能减少停顿的时间。
3、对象引用
Object obj = new Object(); 就是将对象分等级:强引用(有引用指向的对象)、软引用 、 弱引用 、虚引用(这三个都是垃圾了) 【在 java.lang.ref包中】
- 强引用:obj引用创建的对象,那么此对象就是被强引用的,这种情况下,即使内存不够用了,报内存溢出OOM,也不会回收。
- 软引用:当内存足够使用时,先不回收这类对象,当虚拟机内存不够用时,要回收此类对象。
- 弱引用:此类对象只能生存到下次垃圾回收时,只要发生垃圾回收,回收此类对象。
- 虚引用:发现即回收。
4、垃圾回收器
比较底层,了解垃圾回收器的一些种类及实现。 垃圾回收器(具体实现垃圾回收的收集器名称)
(1)垃圾回收器分类
按线程数分,可以分为串行垃圾回收器和并行垃圾回收器。 按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。 按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器。
(2)垃圾回收器的性能指标
吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间) 垃圾收集开销:垃圾收集所有时间与总运行时间的比例。 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。 收集频率:相对于应用程序的执行,收集操作发生的频率。 内存占用:java堆区所占的内存大小。 快速:一个对象从诞生到被回收所经历的时间。
(3) CMS 回收器(并发收集、低延迟)
|