JVM--这一篇就够了

1. JVM内存模型

Java内存模型是指Java虚拟机的内存模型,我们来看下Java内存模型的图片:
JVM内存模型
其中,在JAVA的JVM调优中,我们JAVA程序员需要重点关注的,首先是堆,我们看下堆内存的内存模型:
堆内存


2. 类的加载过程

1. 加载

通过一个类的全限定名获取该类的二进制流,将该二进制流中的静态存储结构转化为方法去运行时数据结构,在内存中生成该类的Class对象,作为该类的数据访问入口。

2. 验证

文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型
元数据验证:对字节码描述的信息进行语义分析,如这个类中是否有父类,是否集成了不被继承的类等
字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等
符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行

3. 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中

4. 解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后

5. 初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码

类加载的补充、、、

1. 类加载检查: 虚拟机遇到一条new指令的时候,首先去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载,解析和初始化过,如果没有,那么必须执行相应的类加载过程
2. 分配内存: 在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需内存的大小在类加载完之后便可以完全确定,为对象分配空间的任务相同于一块确定大小的内存在Java堆中划分出来
这个步骤有两个问题:1、如何划分内存 2、在并发情况下,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同事使用了原来的指针来分配内存的情况
划分内存的方法: 使用CAS的方法保证每次操作都是原子操作
3. 初始化: 内存分配完成之后,虚拟机需要将分配到的内存空间都初始化为零值,如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行,这一步操作保证了对象的实例字段在JAVA代码中可以不赋初始值可以直接使用
4. 设置对象头: 初始化零值之后,虚拟机要对对象进行赋值,例如这个对象是哪个类的实例,如果才能找到类的类源数据信息,对象的哈希吗、对象的GC分代年龄、这些东西都放置在对象头ObjectHeader中
对象在内存中的存储布局可以分为3块区域:对象头、实例数据、对其填充位,HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身运行的数据,如哈希吗、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等、对象的另一部分是类型指针,即对象指向它的类元数据指针,虚拟机通过指针来确定这个对象是哪个类的实例
5. 执行方法: 执行方法,即对象按照开发人员的意思进行初始化,对应到语言层面上讲。就是为属性赋值,执行构造方法

对象在Eden区分配

大多数情况下,对象在Eden区进行分配,当Eden区没有足够的空间进行分配时,虚拟机将发起一次MinitorGC
Eden与Survivor默认大小8:1:1,大量的对象被分配在Eden区,Eden区满了之后会出发miniorGC,可能会有99%的对象会被当成垃圾回收掉,剩余存货的对象会被挪到为空的那块survivor区,下一次Eden区满了又会触发miniorGC,把Eden区和survivor区垃圾对象回收,把剩余存货的对象一次性挪到为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所以比例是8:1:1,一般配置都是Eden尽量大。survivor够用即可。


3、进入老年代的对象

(1)、大对象直接进入老年代

大对象就是需要大量连续空间存储的对象,比如字符串、数组。 JVM参数 -XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置的大小,这个对象不会进入年轻代,这个参数只在Serial和ParNew两个收集器下有效。这样是为了避免大对象分配内存是的赋值操作而降低效率

(2)、长期存活的对象进入老年代

既然虚拟机采用了分代收集的思想管理内存,那么内存回收就必须能够识别哪些对象应该放在新生代,哪些对象应该放在老年代,为了做到这一点,虚拟机给每一个对象记录一个对象年龄的计数器,一个情况下,对象的GC分代年龄大于 15 就会进入老年代。

(3)、对象动态年龄判断机制

当前放对象的 Survivor 区域中,一批对象的总大小大于这块 survivor 的50%,那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了。例如 survivor 区域中有一批对象,年龄1+年龄2—+年龄n的多个年龄综合超过 survivor 区域的50%,此时就会把年龄大于 n 都放入老年代,这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代,对象动态年龄判断机制一般是在minor gc之后触发的。

(4)、老年代空间分配担保机制

年轻代 每次minorGC之前,JVM都会计算下老年代剩余可用空间。如果这个可用空间小于年轻代现有的所有对象之和,就会在miniorGC之前进行一次FullGC。


4、垃圾收集算法

垃圾收集的算法: 分代收集理论、复制算法、标记整理算法、标记清除算法

(1)分代收集算法

分代收集算法,就是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成。而老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收

(2)复制算法

这种算法是把整个年轻代分成From和To相等的两部分,当From部分达到GC阈值时,就将From部分活动内存对象全部复制到To部分。复制完成后,From和To的身份互换。一般用于年轻代垃圾回收

(3)标记整理算法

标记整理算法一般用于老年代垃圾回收。分为三个步骤。

  1. 标记存活对象。 这个步骤采用根可达算法。从GCROOT根出发,找到所有被引用对象。
  2. 整理存活对象。目前存活的对象,可能被分配在内存的不同地方,碎片化较为严重,整理存活对象就是将对象相对集中的存放,在整理同时,会签对象在虚拟机栈中的引用地址也发生改变。
  3. 清除非存活对象。在经过标记和整理后,已经能区分出哪些是存活对象,哪些是垃圾对象,此步骤就是将垃圾对象清除。

标记整理算法相对标记清除算法的优缺点
优点:

  • 没有了碎片化内存(相较于标记清除算法)
  • 没有了内存减半的消耗(相较于复制算法)

缺点:

  • 在整理存活对象时,因为对象位置点变动,还需要该调整虚拟机栈中的引用地址
  • 在整理存活对象时,需要全程暂停用户线程,STW(Stop The World)
  • 效率相比于标记复制算法低一些

(4)标记清除算法

“标记 - 清除”算法是最基础的垃圾收集算法,如同它的名字一样,算法的工作过程可以分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。
缺点:

  • 效率问题:标记和清除两个过程的效率都不高。
  • 空间问题:可能产生大量的不连续的内存碎片,进而导致空间利用率下降,垃圾收集频率变高。

收集算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小故事呀

您的打赏是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值