JVM-内存垃圾回收(二)
一、JVM垃圾回收
1、垃圾回收判定算法,确定对象是否被引用(解决什么时候回收问题)
(1)引用计数法
含义:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
问题:它很难解决对象之间相互循环引用的问题
(2)可达性分析法
基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的
(1)可作为GC Root的对象
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象
(2)引用分类
强引用:强引用就是指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用。只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
软引用:软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
弱引用:弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
虚引用:虚引用是最弱的一种引用关系。虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
2、堆垃圾回收流程(执行finalize()过程)
(1)开启GC线程,找到GC ROOT对象,通过可达性分析后,判断回收区域对象是否被引用
(2)两次标记和筛选。如果未被引用,对对象进行首次标记。通过是否有必要执行finalize()方法进行过滤。
(3)如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,防止finalize()执行缓慢或出现死循环。
(4)GC将对F-Queue中的对象进行第二次小规模的标记,两次标记的对象执行finalize()。
注:在一个线程中,任何一个对象的finalize()方法都只会被系统自动调用一次。
3、方法区(或HotSpot虚拟机中的永久代)垃圾回收
(1)废弃常量
常量池中的字面量(常量或类、方法、字段的符号引用)没有被其他对象进行引用。
(2)无用的类
1.该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
2.加载该类的ClassLoader已经被回收。
3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
4、垃圾收集算法
二、HotSpot虚拟机垃圾回收机制
1、原因(背景)
GC在进行垃圾回收的时候会停掉所有的java线程(问题:在线程执行中什么地方停效率高?)
java应用中GC ROOT引用太多,GC逐一扫描会过多消耗资源时间(问题:怎样快速获取有引用的根节点?)
2、提升GC效率的方式
(1)枚举根节点。HotSpot中使用一组称为OopMap的数据结构来达到这个目的。在栈或寄存器中记录下那些位置时引用。
(2)安全点。安全点的选定是以“是否具有让程序长时间执行的特征”为标准进行选定,“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等。让java线程跑到安全点时通过中断机制停掉线程,就可以开始运行GC。【主要针对进程运行时的GC】
(3)安全区域。GC线程直接对安全区域发起GC。【主要针对程序未运行时的垃圾回收,如进程阻塞、未获得CPU执行权等】
三、垃圾收集器
1、分类
注:垃圾回收的并发是指用户线程和垃圾回收线程可以同时运行;并行是指可以同时开启多个垃圾回收线程。
四、内存分配和回收策略
(1)对象优先在Eden(新生代)分配,通过Minor GC机制回收,回收速度快,频率高
(2)大对象、长期存活的对象直接进入老年代,通过Major GC/Full GC机制回收,速度慢,频率较低
五、java性能监控和优化
(一)jdk命令行工具(二)可视化工具
JConsole
VisualVM
六、JVM调优实战
1、在高性能硬件上部署程序
(1)通过64位JDK来使用大内存。
(2)使用若干个32位虚拟机建立逻辑集群来利用硬件资源。
2、控制FULL GC频率
(1)Java虚拟机分配超大堆的前提是有把应用程序的Full GC频率控制得足够低,譬如十几个小时乃至一天才出现一次Full GC,这样可以通过在深夜执行定时任务的方式触发Full GC甚至自动重启应用服务器来保持内存可用空间在一个稳定的水平。
(2)控制Full GC频率的关键是看应用中绝大多数对象能否符合“朝生夕灭”的原则。大多数对象的生存时间不应太长,这样才能保障老年代空间的稳定。
3、操作系统对每个进程能管理的内存是有限制的。如果进程堆内存、线程堆栈、Socket缓存区、JNI本地库内存、虚拟机和、、GC和堆外内存总计超过最大限制内存也会报内存溢出
堆外内存概述
堆外内存(Direct Memory): 由操作系统分配和释放内存,提高效率,java中使用未公开的Unsafe和NIO包下ByteBuffer使用堆外内存。
通过XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。
堆外内存优势
可以扩展至更大的内存空间。比如超过1TB甚至比主存还大的空间。
理论上能减少GC暂停时间(节约了大量的堆内内存)。
可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现它的持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数据。
堆外内存能够提升IO效率。
堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了数据从用户内向内核态的拷贝。