JVM
2025-01-10 11:07:04 0 举报
AI智能生成
JVM
作者其他创作
大纲/内容
内存分配和回收策略
Minor GC 和 Full GC
Minor GC
新生代
Eden不足时机
Full GC
发生在老年区的GC,出现Full GC时.<br>往往伴随着Minor GC,比Minor GC慢10倍以上。
时机
1.调用System.gc()
只是建议虚拟机执行Full GC,但是虚拟机不一定真正去执行。
不建议使用这种方式,而是让虚拟机管理内存。
程序员自己调用
2.老年代空间不足
常见场景就是大对象和长期存活对象进入老年代
尽量避免创建过大的对象以及数组,调大新生代大小,<br>让对象尽量咋新生代中被回收,不进入老年代。
3.JDK1.7 之前方法区空间不足
当系统中要加载的类、反射的类和常量较多时,<br>永久代可能会被占满,在未配置CMS GC的情况下<br>也会执行Full GC,如果空间仍然不够则会抛出OOM异常
可采用增大方法区空间或转为使用CMS GC
4.空间分配担保失败
发生Minor GC时分配担保的两个判断失败
5. Concurrent Mode Failure
CMS GC 并发清理阶段用户线程还在执行
不断有新的浮动垃圾产生,
当预留空间不足时报Concurrent Mode Failure错误并触发Full GC
内存分配策略
1.对象优先在Eden分配
大多数情况下,对象在新生代Eden上分配,当Eden空间不够时,发起Minor GC,
当另外一Survivor空间不足时则将存活对象<br>通过分配担保机制提前转移到老年代。
2.大对象直接进入老年代
配置参数-XX:PretenureSizeThreshold
大于此值得对象直接在老年代分配,避免在Eden和Survivor之间的大量内存复制。
3.长期存活对象进入老年代
虚拟机为每个对象定义了一个Age计数器,
对象在Eden出生并经过Minor GC存活<br>转移到男一个Survivor空间中时Age++,
增加到默认16则转移到老年代。
4.动态对象年龄绑定
虚拟机并不是永远要求对象的年龄必须到达<font color="#a23c73">MaxTenuringThreshold</font>才能晋升老年代
如果在Survivor中<font color="#a23c73">相同年龄所有对象大小</font>总和大于<font color="#a23c73">Survivor空间的一半,</font><br>则年龄大于或等于该年龄的对象直接进入老年代。
5.空间分配担保
在发生Minor GC之前,虚拟机先检查老年代最大可用的连续空间<br>是否大于新生代的所有对象,
如果条件成立,那么Minor GC可以认为是安全的.可以通过HandlePromotionFailure参数设置允许冒险
此时虚拟机将与历代晋升到老年区对象的平均大小比较,<br>仍小于则要进行一次FulI GC。<br>在JDK1.6.24之后HandlePromotionFailure已无作用,即虚拟机默认为true。
虚拟机把描述类的数据从Class问价加载到内存并对数据进行校验、转换解析和初始化,<br>最终形成可以被虚拟机直接使用的Java类型。ava应用程序的高度灵活性就是依赖运行期动态加载和动态连接实现的。
类的加载机制
概念
生命周期
加载 -> 连接(验证 -> 准备 -> 析) > 初始化 -> 使用 ->卸载
类初始化时机
主动引用
被动引用
类加载过程
1.加载
2.验证
3.准备
4.解析
5.初始化
<clinit>
<init>
类(加载) 器
类与类加载器
类加载器分类
启动类加载器
扩展类加载器
应用程序类加载器
自定义类加载器
双亲委派模型
概念
双亲委派模型要求除了顶层的启动类加载器外,<br>其余的类加载器都应该有自己的父类加载器。<br>父子不会以继承的关系类实现,而是都是<br>使用组合关系来服用父加载器的代码。<br>在iava.lang.ClassLoader的loadClass0方法中实现。
工作过程
一个类加载器首先将类加载请求转发到父类加载器,<br>只有当父类加载器无法完成 (它的搜索范围中没有找到所需要的类)时才尝试自己加载
好处
Java类随着它的类加载器一起具备了一种带有优先级的层次关系,从而使得基础类库得到同意。
运行时数据区域
线程私有
程序计数器
存储当前线程所执行的字节码指令的地址或索引<br>程序计数器的值在线程切换时会保存和恢复,确保每个线程都有独立的计数器,<br>可以独立执行各自的字节码指令,实现线程间的并发执行。<br>
Java虚拟机栈
存储方法执行时的局部变量、方法参数、方法调用和返回的信息。通过<b><u>-Xss</u></b>参数来指定<br>虚拟机栈的大小是固定的,栈帧(Stack Frame)在方法调用和返回时会入栈和出栈,<br>确保方法的局部变量和执行状态的隔离和独立性。局部变量过多时,可能会导致栈溢出<br>
本地方法栈
本地方法栈与Java虚拟机栈类似,他们之间区别只不过是本地方法栈为Native方法服务。<br>java的作用域范围答不到了,它会调用底层语言的库。会进入本地方法栈。调用本地方法本地接口,叫做JNI<br>JNI作用:扩展java的使用,融合不用的编程语言为java使用,如C或C++<br>
线程共有
Java堆(GC区)
用于存储对象实例和数组 包括垃圾收集机制 <br>-Xms参数和-Xmx参数
新生代
Eden空间 8
新创建的对象首先会被分配到Eden空间
当Eden空间被填满时,会触发一次垃圾回收(Minor GC)。
复制算法(Copying Algorithm)
From survivor空间 1
Survivor区域无法容纳所有的存活对象,并且老年代也无法容纳这些对象<br>,那么可能会触发Full GC,对整个堆进行垃圾回收,以清理无法回收的对象。
ToSurvior 空间 1
To Survivor区域满了且无法容纳更多存活对象时,<br>垃圾回收器会将这些对象直接晋升到老年代
老年代
例如全局变量、静态变量、长时间存活的对象等。这些对象在内存中存活时间较长,不易被回收。
经过多次垃圾回收后仍然存活
垃圾回收算法(如标记-清除算法、标记-整理算法)则较少执行,|<br>因为老年代中的对象存活时间长,垃圾回收频率较低
Tenured区
用于存放老年代的对象
<ul><li><span style="font-size: inherit;">对象年龄达到阈值</span></li><li><span style="font-size: inherit;">内存不足 XX:CMSInitiatingOccupancyFraction来设置老年代的空间使用率阈值</span></li><li><span style="font-size: inherit;">手动调用:通过调用System.gc()方法,</span></li></ul>
JDK1.7方法区 (永久代)
从1.8之后 移除永久代 把方法去移动到元空间
用于存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
它的大小可以通过 -XX:PermSize 和 -XX:MaxPermSize 参数进行调整
Java 虚拟机启动时被创建
运行时常量池
各种字面量和符号引用
class 文件中的常量池 编译器生成的字面量 和符号引用 允许动态生成
JDK 1.8 元空间
原本的方法区的数据
一部分 放到本地内存而不是虚拟机内存
另一部分 元空间存储的类的元信息、静态变量、常量池放到堆中
直接内存
在NIO中,会使用 Native 函数库直接分配堆外的内存
HotSpot 虚拟机
对象的创建
<span style="font-size: inherit;">当 new一个对象时:</span><br><ul><li>检查参数是否在常量池中找到符号因引用,检测这个符号是否被加载、解析、初始化 没有的话先执行类的加载过程</li><li>类的加载器检查过后 虚拟机为新生成的对象分配内存</li><li>内存分配完成后 都初始化为零 (不包括对象头)</li><li>对象头设置</li><li>执行构造方法</li></ul><br>
对象的内存分布
对象头
1.第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向实现戳等<br>2.第二部分是类型指针,即对象指向它的类元数据的指针 (如果使用直接对象指针访问),<br>虚拟机通过这个指针来确定这个对象是哪个类的实例。<br>3.如果对象是一个Java数组的话,还需要第三部分记录数据长度的数据。
实例数据
真正存储的有效信息,也就是代码中各类型的字段信息
对其填充
hotspot 要求对象的大小必须是8字符的整数倍
对象的访问定位
句柄访问
在Java堆中划分出一块内存作为句柄池Java栈上的对象引用reference中存储的就是对象的句柄地址,<br>而句柄中包含了到对象实例数据的指针和到对象类型数据的指针。<br>对象实例数据在Java堆中,对象类型数据在方法区 (永久代) 中。<br><br>优点: 在对象被移动时只会改变句柄中的实例数据指针,而对象引用本身不需要修改
直接指针访问
Java栈上的对象用reference中存储的就是对象的直接地址。<br>在堆中的对象实例数据就需要包含到对象类型数据的指针,<br><br>优点:节省了一次指针定位的时间开销,速度更快
垃圾收集
概念
垃圾收集主要是针对Java堆和方法区。<br>程序计数器、Java虚拟机栈、本地方法栈 三个区域属于线程私有,线程或方法结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
判断对象是否可以被回收
第一次标记(缓刑)
引用计数算法 (已淘汰)
给对象一个引用计数器,引用++ 失效-- 引用计数为0 可以被回收
对象相互引用 即使引用失效 也不会被回收 存在弊端
可达性分析算法(Java使用)
一系列GC Roots 的对象作为起点,引用链 一个对象没有任何的引用链 表示可以被回收
GC roots 对象包括:<br><ol><li><span style="font-size:inherit;">虚拟机栈中引用的对象</span></li><li><span style="font-size:inherit;">方法区中 公类静态属性引用的对象</span></li><li><span style="font-size:inherit;">方法区中 常量引用的对象</span></li><li>本地方法区中 JNI native 方法引用的对象</li></ol>
第二次标记
finalize()函数
在进行gc之前会先调用对象的finalize方法,是object 的方法,可以在类中重写进行一些操作,如果未对其进行重写就会执行父类中的finalize方法,object默认的finalize方法是没有任何操作的
方法区的回收
在方法区进行垃圾回收的性价比一般比较低。
主要回收两部分,废弃常量和无用的类。<br>满足无用的类三个判断条件才仅仅代表可以进行回收,不是必然关系,可以使用-Xnocassgc参数控制。<br>1.该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例。<br>2.加载该类的ClassLoader已经被回收<br>3.该类对应的iava。lang。Class对象没有在任何地方被引用,无法在任何地方通过反射访问到该类方法区的回收的方法。
引用类型
强引用:无法回收,即使oom也不会回收
软引用:正常gc不会回收,但是当内存不足时会回收
弱引用:gc就会回收
虚引用:随时回收
垃圾收集算法
标记-清除
老年代
1,效率问题,标记和清除两个过程的效率都不高。<br>2,空间问题,标记清除之后会产生大量不连续的内存碎片,没有连续内存容纳较大对象,而不得不触发另一次的垃圾回收
标记-整理
老年代 把那些gc后的空间碎片 向一个方向移动 内存地址依次排 更新对应引用的指针 清除最后一个内存地址之外的所有空间
弥补标记清除算法出现空间碎片的风险
移动大对象也有效率问题
复制
新生代
每次只用一个内存空间 保证一个survivor区是空闲的,当其中一个内存空间快满了survivor 90% <br>jvm会停止此线程的执行 开启GC线程的执行 将还存活的对象复制到另一块空间。<br>在复制完成后,再把使用过的空间一次性清理掉,对象会严格按照内存地址依次排列,<br>同时gc线程会更新存活对象的内存引用地址,指向新的内存地址
分代收集
主要目的是:<br>根据对象的生命周期将内存划分为不同的区域,并针对不同区域使用不同的垃圾回收策略,以提高垃圾回收的效率和性能。
把堆内存空间分为为青年代和老年代 每个代 用不同用的算法
Hosport 算法的实现
枚举根节点(GC Roots)
安全点
安全区域
垃圾回收器
青年代
1,serial收集器
2,parnew收集器
3,parallel svavenge 收集器
老年代
4,serial old收集器
是Serial收集器老年代版本。<br>也是给Client场景下的虚拟机使用的
5,parallel old 收集器
6,cms收集器
Concurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器
运作过程
优缺点
7,g1回收器
Garbage First是一款面向服务端应用的垃圾收集器
运作过程
1.初始标记<br>2.并发标记<br>3.最终标记<br>4.删选标记
0 条评论
下一页