JVM和GC原理
2022-05-04 10:51:03 14 举报
AI智能生成
Java内存介绍,jvm和垃圾回收
作者其他创作
大纲/内容
Java代码执行
代码编译
javac
类加载<br>当程序主动使用某个类时,如果该类还未被加载到内存中,<br>则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。<br>如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化<br>
类的生命周期
加载<br><font color="#ffb74d">在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的入口</font><br>
class
jar
war
zip
连接
验证<br><font color="#ffb74d">确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求<br>并且不会危害虚拟机自身的安全</font><br>
文件格式验证
元数据验证
字节码验证
符号引用验证
准备<br><font color="#ffb74d">为类变量分配内存,准备初始值<br>对final的静态字面值常量直接赋初值</font><br>
解析<br><font color="#ffb74d">将符号引用转换成直接引用</font>
符号引用<br><ol><li><font color="#ffb74d">符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟 机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引 用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。</font></li><li><font color="#ffb74d">CONSTANT_Class_info</font></li><li><font color="#ffb74d">CONSTANT_Field_info</font></li><li><font color="#ffb74d">CONSTANT_Method_info</font></li></ol>
直接引用<br><font color="#ffb74d">直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有 了直接引用,那引用的目标必定已经在内存中存在。</font><br>
初始化<br><font color="#ffb74d">为静态变量赋值</font>
不初始化的场景(不初始化不代表不执行上面的加载和链接步骤)<br><ol><li><font color="#ffb74d">通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。</font></li><li><font color="#ffb74d">定义对象数组,不会触发该类的初始化。</font></li><li><font color="#ffb74d">常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触</font></li><li><font color="#ffb74d">发定义常量所在的类。</font></li><li><font color="#ffb74d">通过类名获取 Class 对象,不会触发类的初始化。</font></li><li><font color="#ffb74d">通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。</font></li><li><font color="#ffb74d">通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。</font></li></ol>
使用
卸载
类加载的时机
主动引用<br><font color="#ffb74d">加载(loading)阶段,java虚拟机规范中没有进行约束,但初始化阶段,<br>java虚拟机严格规定了有且只有如下5种情况必须立即进行初始化(初始化前,必须经过加载、验证、准备阶段)<br></font><ol><li><font color="#ffb74d">使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。</font></li><li><font color="#ffb74d">对内进行反射调用时。</font></li><li><font color="#ffb74d">当初始化一个类时,如果父类没有进行初始化,需要先初始化父类。</font></li><li><font color="#ffb74d">启动程序所使用的main方法所在类</font></li><li><font color="#ffb74d">当使用1.7的动态语音支持时。</font></li></ol>
被动引用<br><ol><li><font color="#ffb74d">通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。</font></li><li><font color="#ffb74d">定义对象数组和集合,不会触发该类的初始化</font></li><li><font color="#ffb74d">类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化)</font></li></ol>
类加载的方式
隐式加载<br><ol><li><font color="#ffb74d">创建类对象</font></li><li><font color="#ffb74d">使用类的静态域</font></li><li><font color="#ffb74d">创建子类对象</font></li><li><font color="#ffb74d">使用子类的静态域</font></li><li><font color="#ffb74d">在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class</font></li><li><font color="#ffb74d">在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class</font></li><li><font color="#ffb74d">在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件</font></li></ol>
显示加载<br><ol><li><font color="#ffb74d">ClassLoader.loadClass(className),只加载和连接、不会进行初始化</font></li><li><font color="#ffb74d">Class.forName(String name, boolean initialize,ClassLoader loader); 使用loader进行加载和连接,根据参数initialize决定是否初始化。</font></li></ol>
执行class
解释执行
编译执行
内存管理
内存空间
线程私有
程序计数器<br><font color="#ffb74d">PC</font>
指向虚拟机字节码指令的位置
唯一一个无OOM的区域
虚拟机栈 <br><font color="#ffb74d">VM stack</font><br>
虚拟机栈和线程的生命周期相同
一个线程每调用一次方法就创建一个栈祯
栈祯的结构
本地变量表
操作数栈
运行时常量池的引用
返回地址
异常
线程请求的栈深度大于JVM所允许的深度 StackOverflowErroe
无法申请足够的内存 OOM
本地方法栈<br><font color="#ffb74d">Native Method Stack</font>
线程共享
永久代<br><font color="#ffb74d">存储被JVM加载的类信息,常量,静态变量,即时编译后的代码<br>java8之后被元数据取代,使用直接内存,字符串池和静态变量放到堆中管理</font>
运行时常量池
堆<br><font color="#ffb74d">由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: <br>新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代</font><br>
新生代<br><font color="#ffb74d">复制算法</font>
eden
from survivor
to survivor
老年代<br><font color="#ffb74d">标记清除算法</font>
直接内存
不受jvm gc 管理
内存分配
堆上分配
TLAB
栈上分配
内存回收GC
GC要做的三件事?
那些内存需要回收
什么时候回收
怎么回收
哪些对象已经死亡
引用计数法<br><font color="#ffb74d">Reference Count</font>
循环引用问题
根搜索算法<br><font color="#ffb74d">GC Roots tracing<br>通过一系列成为GC roots的点作为起点,向下搜索<br>当一个对象到任何GC roots没有引用链,说明起已经死亡</font>
VM 栈中的引用
方法区中的静态引用
JNI中的引用
垃圾回收算法<br><font color="#ffb74d">每次清除新生代,年龄+1.大于15直接放入老年代<br>对象占用内存太大,直接到老年代<br>老年代放不下,触发Major GC</font>
复制算法<br><font color="#ffb74d">copying<br>效率高,无碎片化,空间被压缩,利用率不高<br>如果存活对象增多,效率会变低</font><br>
新生代,朝生夕死
标记清除<br><font color="#ffb74d">Mark-Sweep<br>内存碎片化</font><br>
标记需要清理的对象
清除对象
标记整理<br><font color="#ffb74d">Mark-Compact</font><br>
标记
将待清除的移动到另外一边
清理边上的对象
分代收集
垃圾回收器
Serial
单线程,复制算法,新生代,对于单核的处理器,少了上下文切换,效率高
ParNew
多线程版本的复制算法,在回收stop the world时是多线程的
Parallel Scavenge
多线程复制算法,关注吞吐<br><font color="#ffb74d">吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)), <br>高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务</font><br>
Serial Old
老年代,单线程标记整理算法<br><font color="#ffb74d">与新生代Parallel Scavenge搭配使用<br>作为老年代CMS的备用收集器</font><br>
Parallel Old
Parallel Scavenge的老年代版本,多线程标记整理算法。<br><font color="#ffb74d">在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞 吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略。</font><br>
CMS<br>并发标记清除<br>老年代多线程标记清楚算法,以获取最短停顿时间<br>
初始标记
只标记GC Roots能直接关联的对象,速度很快 stop the world
并发标记
进行GC roots跟踪过程,和用户线程一起工作,不需要stw
重新标记
修正并发标记时,因用户线程继续运行而导致标记产生的变动的那一部分标记记录,STW
并发清除
并发清除对象,和用户线程一起工作,不需要STW,时间比较长
G1
参数
Xms
Xmx
Xmn
-XX:PrintCDetails
-XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=xxx
-XX:MaxTenuringThreshold
-XX:-HandlePromotionFailure
内存状况分析
jconsole
visualvm
jstat
jmap
MAT
JMM
Subtopic
Subtopic
0 条评论
下一页