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