目录
[1.1.JVM 组成](#1.1.JVM 组成)
[二、Garbage 垃圾确定方法](#二、Garbage 垃圾确定方法)
[2.1.1标记-清除 Mark-Sweep](#2.1.1标记-清除 Mark-Sweep)
[2.1.2.标记压缩 (压实)Mark-Compact](#2.1.2.标记压缩 (压实)Mark-Compact)
[2.1.3 复制 Copying](#2.1.3 复制 Copying)
[2.1.5 STW](#2.1.5 STW)
一、JVM
1.1.JVM 组成
-
类加载子系统: 使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文件。class loader类加载器将所需所有类加载到内存,必要时将类实例化成实例
-
运行时数据区: 最消耗内存的空间,需要优化
-
执行引擎: 包括JIT (JustInTimeCompiler)即时编译器, GC垃圾回收器
-
本地方法接口: 将本地方法栈通过JNI(Java Native Interface)调用Native Method Libraries, 比如:C,C++库等,扩展Java功能,融合不同的编程语言为Java所用
1.2.JVM运行时数据区域由下面部分构成
-
**Method Area (线程共享):**方法区是所有线程共享的内存空间,存放已加载的类信息(构造方法,接口定义),常量(final),静态变量(static), 运行时常量池等。但实例变量存放在堆内存中. 从JDK8开始此空间由永久代改名为元空间
-
heap (线程共享): 堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出OOM异常.堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小
-
**Java stack (线程私有):**Java栈是每个线程会分配一个栈,存放java中8大基本数据类型,对象引用,实例的本地变量,方法参数和返回值等,基于FILO()(First In Last Out),每个方法为一个栈帧 1 50 %
-
**Program Counter Register (线程私有):**PC寄存器就是一个指针,指向方法区中的方法字节码,每一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令.因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了
-
**Native Method stack (线程私有):**本地方法栈为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。
二、Garbage 垃圾确定方法
-
引用计数: 每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。Python中即使用此种方式。 简单来说就是有个笔记本,记录有没有人在用,缺陷,AB 资源互相调用
-
根搜索(可达)算法 Root Searching
2.1.垃圾回收基本算法
2.1.1标记-清除Mark-Sweep
分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,对未标记对象(即不再使用的对象)逐一进行清理。
标记-清除最大的问题会造成内存碎片,但是不浪费空间,效率较高(如果对象较多,逐一删除效率也会影响)
2.1.2.标记压缩 (压实)Mark-Compact
分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。
标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。缺点是内存整理过程有消耗,效率相对低下
2.1.3 复制 Copying
先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。缺点是比较浪费内存,只能使用原来一半内存,因为内存对半划分了,复制过程毕竟也是有代价。好处是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以效率很高。
2.1.4多种算法总结
没有最好的算法,在不同场景选择最合适的算法
-
效率: 复制算法>标记清除算法> 标记压缩算法
-
内存整齐度: 复制算法=标记压缩算法> 标记清除算法
-
内存利用率: 标记压缩算法=标记清除算法>复制算法
2.1.5 STW
对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC 完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。
三、分代堆内存GC策略
3.1.年轻代回收Minor GC
-
起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动* *GC**。这个称为** Young GC 或者 Minor GC。
-
先标记 eden存活对象,然后将存活对象复制 到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都清空。GC完成。
-
继续新建对象,当eden再次满了,启动GC。
-
先同时标记 eden和s0中存活对象,然后将存活对象复制 到s1。将eden和s0清空,此次GC完成
-
继续新建对象,当eden满了,启动GC。
-
先标记 eden和s1中存活对象,然后将存活对象复制 到s0。将eden和s1清空,此次GC完成以后就重复上面的步骤。
通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值(默认15次,CMS为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到老年代。
3.2.老年代回收 Major GC
进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。如果老年代也满了,会触发老年代GC,称为Old GC 或者 Major GC。
由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法。
当老年代满时,会触发 Full GC,即对所有"代"的内存进行垃圾回收
Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full GC。所以可以认为 MajorGC = FullGC