学习总结Java虚拟机(JVM)

前言

本文是学习了《深入理解Java虚拟机》第三版之后的感受,在此也作为个人总结,也用于回顾梳理,分享给大家~ 如果哪里有问题,还望指正!最后会提供一些参考链接。本文持续更新

1.jvm

1.1 jvm简介

  1. Java Virtual Machine(Java虚拟机)jvm是虚拟出来的计算机,运行在操作系统之上的。通过类加载器(Class Loader)将class文件加载,通过jvm解析成机器码、执行引擎(Execution Engine)执行与具体平台上的机器进行交互。这就是为什么java可以“一次编译,到处运行”的原因。

1.2 jvm结构图

在这里插入图片描述

1.2.1. 程序计数器

定义:较小的、具有记录性的、线程私有的、内存空间。

工作原理:通过改变计数器的值,选取下一条需要执行(分支、循环、跳转、异常处理等)的字节码指令。

线程私有:java多线程是轮流切换执行的,为了下一次切换,准确定位到正确的位置,每个线程都要有独立的程序计数器。

:执行为Native方法时,计数器值为Undefind;正在执行为java方法时,值为正在执行的字节码指令的地址

特点:内存区域中,唯一一个没有规定任何OutOfMemoryError(内存溢出)情况的区域

1.2.2. java栈

在这里插入图片描述
定义:表示java方法运行的内存模型。

工作原理:当每一个方法入栈时,同步创建一个栈帧(Stack Frame),栈帧用于存储局部变量表、动态链接、操作数帧、方法出口等信息。局部变量表中,存储着基本数据类型、对象引用(referece类型)、以及返回指向另一个字节码指令的地址。这些类型的存储空间用变量槽(Slot)来表示,基本数据类型中的doubel、long类型的数据占用2个变量槽。其他类型的数据只占用1个变量槽。并且,栈帧中分配的局部变量空间是完全固定的。分配的是变量槽的数量。内存的大小完全是由jvm自行决定。

线程私有:栈的生命周期和线程一样。

特点 :HotSpot虚拟机栈容量是不可以扩展的,所以线程一但申请栈空间成功,就不会出现OutOfMesoryError,不成功自然会OOM。

1.2.3. 本地方法栈

定义:执行本地的(Native)方法。

1.2.4. 堆

定义 : 存放“几乎”所有的java对象实例的、线程共享的内存空间,主要进行垃圾回收,又称GC(Garbage Collected Heap)堆
GC工作原理:对象创建分配内存空间,并进行内存自动化管理,进行对象选择回收。注:学习GC,1.先要了解垃圾收集算法,在了解常见的垃圾收集器。最后分析垃圾回收的流程。
特点 :没有规定内存空间是否连续,理论上是需要的。并且也是作为java开发的最重要学习的一部分。

1.2.5. 方法区

定义 :存放已被加载的常量、静态变量、代码缓存等数据的、线程共享的、内存区域。
特点 :基于jdk8,方法区并不是所谓的“永久代”,并且在jdk8中完全废弃此概念。此区域并没有强制规定需要实现垃圾回收。

1.2.6. 运行时常量池

定义:方法区的一部分,用于存放类版本、字段、方法、接口等描述信息,存放常量池表:用与存放编译期生成的各种字面量与符号的引用。类加载后,这部分信息,将会存放到方法区运行时常量池中。

2. 对象

2.1.对象概述

2.1.1.对象的创建

对象创建流程-----话不多说,直接上图(自己画图整理,更清晰)
在这里插入图片描述

2.1.1.对象的内存布局、访问定位

对象包含三部分内容:1.对象头、2.实例数据、3.对齐填充。

  1. 对象头包含俩类信息:自身信息(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间等)、类型指针
  2. 实例数据:定义的各种类型字段内容。
  3. 对齐填充:占位符,对象实例数据没有对齐,需要通过对齐填充来补全。

访问定位包含:句柄访问(java栈中的reference类型存储句柄地址)、直接指针访问(java栈中的reference类型存储对象地址)。

2.2.对象已死?

jvm是如何断定对象是否需要回收呢?

2.2.1.引用计数法

概念:在对象中增添一个引用计数器,当这个对象被引用时,计数加1,引用失效时,计数减1。当计数为0时,对象已不被在使用。
不足点:当对象之间相互引用,使用此方法,对象无法回收。

2.2.2.可达性分析法

通过一系列的 "GC Roots"的根对象作为起始节点集。从这里开始向下搜索。走过的路径叫做“引用链”,如果某个对象到“GC Roots”没有任何的引用链(不可达),则说明此对象不在被使用。

2.2.3.在谈引用

以上判定对象是否存活其实都和“引用”有关系。那么,所谓的一些“食之无味,弃之可惜”的对象处在“引用”和“不被引用之间”就显得很尬,所以下面将引用分成四种。引用强度从高到低
强引用:和引用类似,只要是强引用的对象,都不会被GC。
软引用:非必须对象,在系统内存溢出前,可GC。
若引用:非必须对象,在第二次GC时,无论内存是否不足,都被GC。
虚引用:虚无飘渺,在被GC时,发出一个系统通知。

2.4.生存还是死亡?自我拯救!?

在可达性算法中,如果没有可到达“GC Roots”的引用链,则被标记第一次,在一次筛选,则查看是否有必要执行finalize()方法,如果没必要(没有finalize(),或者已经被执行过),直接Over,反之,将这些对象放在F-Queue队列中,且仅执行一次finalize()方法(不会等待执行完,防止队列阻塞),判断是否能够自我拯救~(只要能重新与引用链上的任何一个对象关联

3.垃圾回收算法

3.1. 标记清除算法

1.场景:
当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

2.执行流程:
标记:遍历所有的GC Roots,标记所有可存活的对象。
清除:遍历所有对象,将没被标记的对象全部清除。

3.缺点:
执行效率太低(递归与前堆遍历),执行GC需要停止整个程序
执行完毕,内存空间不连续。

3.2. 复制算法

1.场景:
内存空间被分成2个空间,其中一空间存放对象(活动空间),另一个则空闲(空闲空间)。
在有效内存被使用完,jvm复制算法进行GC。
2.执行流程:
将所有活动空间的对象复制到空闲空间,并且按照严格的内存地址排列。与此同时,清除不可用对象,更新存活对象的内存地址指向新的地址。最后,活动空间变成空闲,空闲变成活动空间。
3.缺点:
内存被划分成2等份,资源利用率很低,并且假设在对象存活率为100%,那么资源的浪费将不可忽视。
所以这种适合存活率较低的对象,并且要克服50%的空间浪费。

3.3. 标记整理算法

1.场景:
当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(stop the world),然后进行两项工作,第一项则是标记,第二项则是整理。
2.执行流程:
标记:遍历所有的GC Roots,标记所有可存活的对象。
整理:移动所有存活的对象,并安装内存地址排序,清除全部未存活对象。
3.缺点:
效率比较低

3.4. 三种基础算法总结

三种算法共同点:三个算法都基于根搜索算法去判断一个对象是否应该被回收。GC都需要暂停应用程序(stop the world)。
效率:复制算法>标记整理算法>标记清除算法
内存利用率:标记整理算法=标记清除算法>复制算法
内存整齐度:复制算法=标记整理算法>标记清除算法

3.5. 分代搜集算法

在这里插入图片描述
图上,可以看出,内存被分成新生代和老年代,新生代中又被分成Eden(伊甸园)、Survivor区:From(s0)、To(s1),对象初步创建时(👆2.1.1.对象:详解对象创建),将存在Eden和其中的一块Servivor中,可用空间不足时,将进行GC(复制算法,年龄+1),将存活对象全部复制到另一块Sevivor中,直接清除到Eden和原来的其中已用过的Servivor空间,当Servivor区不够容纳一次minor GC之后存活的对象时,直接分配担保进入老年代,反之,minor GC数次(默认年龄为15)后,存活的对象进入老年代。老年代中可进行(标记-整理算法)Major GC或者 Full GC。

4. 垃圾回收器

4.1.新生代收集器:Serial、ParNew、 Parallel Scavenge

Serial:是一种“单线程”的、作用在新生代的回收器,只使用单核单线程执行,并且,在执行过程中,必须要停止其他全部线程。可用于回收几十M或几百M的新生代内存空间。是最基础的垃圾回收器。

ParNew:多线程的、作用在新生代的回收器,在Serial上,增添可控参数配置。在单核的环境下,效果达不到Serial收集器。jdk7前主流的是ParNew+CMS组合, jdk9之后,官方推荐不采用ParNew+CMS的组合,采用G1全堆的处理方式。

Parallel Scavenge:和ParNew类似,一样是多线程回收器。但是着重点不在“Stop The World”的时间短,而是在于吞吐量,参数可配置吞吐量大小、最大的垃圾回收时间。还有一点就是在于可自适应调节策略把内存交给虚拟机来完成。只要设置初始值就行。

以上均基于新生代,采用复制算法

4.2.老年代收集器:Serial Old、Parallel Old、CMS(Concurrent Mark Sweep)

Serial Old:单线程、基于标记-整理算法的收集器。jdk1.5以及以前,主要是和Parallel Scavenge搭配使用。(Parallel Scavenge本身自带老年代收集器,并不是一直调用Serial Old)其次就是作为CMS的替代品。

Parallel Old:jdk1.6之后出现的老年代、多线程并发,基于标记-整理算法的收集器。在注重吞吐量、和资源稀缺的环境下,可才用Parallel Scavenge + Parallel Old 组合方式。

CMS:老年代,多线程并发,基于标记-清除算法的收集器。过程为: 初始标记、并发标记、重复标记、并发清除。其中,初始标记、重复标记是需要“Stop The World”但停顿时间很短。也称为“并发低停顿收集器”。
缺点:

  1. 对于处理器资源十分敏感。在处理器核心数为4个或者4个以上,只会占用不超过25%的处理器资源,但是少于4个,CMS处理就会严重影响处理器运算资源。
  2. 没法处理“浮动垃圾”(CMS是并发处理的,程序运行产生新的垃圾,这些垃圾,只能通过下一次GC),有可能出现并发失败(Concurrent Mode Failure),只能采用Serial Old 代替。所以-XX:CMSInitiatingOccupancyFraction(jdk5默认是68%,jdk6默认为92%,也就是老年代内存空间超过xx%自动执行GC)设置的不能过大。
  3. 采用的是标记-清除算法,会产生空间碎片。
4.4.G1

5.参考

参考书籍:《深入理解java虚拟机》第三版
参考链接: https://www.cnblogs.com/zuoxiaolong/category/508918.html
参考链接: https://blog.csdn.net/qq_41701956/article/details/81664921
感谢浏览!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值