JVM基础总结

JVM基础总结

1、JVM系统架构图

1.1 JVM的主要组成部分

JVM包含两个子系统:

  • Class loader(类装载)

  • Execution engine(执行引擎)

两个组件:

  • Runtime data area(运行时数据区):JVM的内存,包含方法区、堆、虚拟机栈、本地方法栈、程序计数器。

  • Native Interface(本地接口):与native libraries交互,是其他编程语言交互的接口。

java程序的运行机制

首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,如果有调用其他语言的部分,可以通过本地库接口(Native Interface)来实现整个程序的功能。

在这里插入图片描述

2、类加载器

通过类的权限定名获取该类的二进制字节流的代码块。

类加载过程:加载、验证、准备、解析和初始化

类加载器分类

  • 启动类加载器(Bootstrap ClassLoader):加载java核心类库。
  • 扩展类加载器(Extension ClassLoader):加载java的扩展库。
  • 系统内类加载器(System ClassLoader):根据java应用的类路径(CLASSPATH)来加载java类。

双亲委派机制

当一个类加载器收到类加载请求时,它首先不会自己去加载这个类,而是将其委派给父类加载器去完成,每一层都如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,如果此时父类无法完成加载请求,子加载器才会尝试去加载类。

双亲委派机制的优点:

  • 基于双亲委派模型规定的这种带有优先级的层次性关系,虚拟机运行程序时就能避免类的重复加载。
  • 双亲委派模型能够避免核心类篡改。

img

沙箱安全机制

沙箱机制将java代码限定在jvm特定的运行范围内,严格限制代码对本地资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

3、JVM内存(运行时数据区)

java虚拟机内存区域划分

方法区、堆、虚拟机栈、本地方法栈、程序计数器

  • 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,是被所有线程共享的

  • :java虚拟机中内存最大的一块,是被所有线程共享的存储对象实例的区域(包括静态对象)。

  • 虚拟机栈:用于存储局部变量、操作数栈、动态链接、方法出口等。

  • 本地方法栈:调用native服务,为本地方法服务。

  • 程序计数器:当前线程所执行的字节码的行号指示器,记录方法之间的调用和执行情况。用来存储指向下一条指令的地址,也即为执行的代码

堆和栈区别

  • 栈使用的数据结构里的栈,先进后出,物理地址分配是连续的。

  • 堆在内存中物理地址分配是不连续的,是在运行期确认的,大小不固定。而栈在内存中是连续的,在编译期就确认,大小是固定的。

  • 存放的内容不同,堆中存储的是对象实例,栈主要存储局部变量、操作数栈、返回结果

队列和栈区别

  • 队列:先进先出
  • 栈:后进先出

内存分配方式

  • 指针碰撞:如果内存规整,把指针往空闲空间移动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”
  • 空闲列表:如果内存不规整,虚拟机必须维护一个列表记录哪些内存是可用的,在分配是从列表找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”

由java堆是否规整来决定,而java堆是否规整由所采用的垃圾回收器是否带有压缩整理功能决定。

解决对象创建的并发安全问题(保证线程安全方式)

  • 同步处理:采用CAS+失败重试,保证更新操作的原子性
    • CAS:属于乐观锁的一种实现。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
  • 本地线程分配缓冲:把内存分配的动作按照线程划分在不同的空间中进行,即在每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲

对象实例的访问定位方式

  • 句柄:java堆划分一块内存作为句柄池,引用中存储对象的句柄地址。句柄中包含了对象实例数据和对象类型数据各自的具体地址信息。引用中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而引用本身不需要修改。
  • 直接指针:引用中存储的直就是对象地址,优点是速度更快,可以节省一次指针定位的时间开销。HopSpot就是采用这种方式

内存泄露

  • 内存泄露:不再被使用的对象或变量一直占据在内存中。(长生命周期的对象持有短生命周期对象的引用)

4、垃圾回收器(GC)

在JVM中,有一个垃圾回收线程,它是低优先级的,正常情况下不会执行,只有在虚拟机空闲或当前堆内存不足时,才会自动触发垃圾回收。

回收内存的目的。使java程序员不再需要考虑内存管理的问题,可以有效的防止内存泄露。

垃圾回收:

  • 分代复制垃圾回收
  • 标记垃圾回收
  • 增量垃圾回收

JVM垃圾回收算法(root根搜索方法)

  • 标记-清除算法:标记无用对象,然后进行清除回收。实现简单,不需要对象进行移动,但标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。(老年代垃圾回收)
  • 复制算法:把内存空间划分为两个相等的区域,每次只使用其中一个区域,垃圾回收时,遍历当前使用区域,把正在使用的对象复制到另一个区域中,最后将当前使用的区域的可回收的对象进行回收。(年轻代垃圾回收)
  • 标记-整理算法:结合标记-清除算法和复制算法的优点。第一阶段,从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记对象,并且把存活对象压缩到堆的其中一块,按顺序排放。(老年代垃圾回收)
  • 分代收集算法:根据对象的存活周期将内存划分为几块,一般为年轻代、老年代和永久代

JVM中的垃圾回收器

  • 新生代:Serial、ParNew、Parallel Scavenge

  • 老年代:CMS、Serial Old、Parallel Old

  • 整堆回收器:G1

img

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效
  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
  • G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

分代垃圾回收器工作原理

分代回收器有两个分区:新生代和老年代,新生代默认的空间占1/3,老年代占2/3。新生代使用垃圾回收算法是复制算法老年代使用标记整理算法新生代有3个分区:Eden、From Survivor、To Survivor,默认占比8:1:1,执行流程如下:

  • 触发Minor GC,把Eden+From Survivor存活的对象放入To Survivor区

  • 清空Eden和From Survivor分区

  • From Survivor和To Survivor分区交换。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

老生代当空间占用到达某个值之后就会触发全局垃圾收回收(Full GC),一般使用标记整理的执行算法。

img

判断对象回收的方法

  • 引用计数器法:为每个对象创建一个引用计数,当计数器为0是就可以被回收,引用被释放时计数-1。缺点是不能解决循环引用的问题。
  • 可达性分析算法:从GC Roots开始向下搜索,所走过的路径为引用链,如果一个对象实例没有任何引用链,则说明该对象可以被回收。
  • 方法区的回收:因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。主要是对常量池的回收和对类的卸载。
  • finalize():用于关闭外部资源,该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。

参考文献

  1. Java虚拟机(JVM)面试题(2020最新版):https://thinkwon.blog.csdn.net/article/details/104390752
  2. JAVA中的CAS:https://blog.csdn.net/weixin_37598682/article/details/81285176
  3. https://www.cnblogs.com/czwbig/p/11127124.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值