必知必会系列-JAVA虚拟机原理

系列文章

引言

随着技术的不断演进,在不同时间阶段都会有不同的技术产物,那么如何快速的学习和掌握新的技术对于技术同学而言是非常重要的,如果我们能够带着问题去学习了解一门技术为什么存在,解决什么问题?如果不使用对我们的影响是什么?然后针对每一个技术最小化模型是什么,思考如果自己做会怎么做,可能会面临哪些问题,然后带着问题去学习每一门是如何解决的。下面是我在学习JAVA虚拟机过程中的一些关键问题,大家可以尝试下解答下,下面有针对每一个提问有对应的解答提示。也欢迎沟通交流反馈你在学习过程中所遇到的问题。

  • 简单介绍下JVM的内存区域如何分布?
  • 哪些内存区域是线程共享,哪些是线程私有的?
  • 简述下JVM类加载过程?
  • 双亲委派机制具体流程?核心的目的是什么?
  • 如何识别已分配内存的对象什么时候回收?
  • 垃圾回收算法是否有具体了解过?
  • 常用的虚拟机命令有哪些?
  • 一般什么情况会造成内存泄露?
  • CPU 100%的问题一般如何排查?

1. 简单介绍下JVM的内存结构分布?

JVM虚拟机运行时的内存区域主要包含:堆、虚拟机栈、本地方法栈、方法区、程序计数器,不同区域的内存的功能是什么?一句话总结就是JAVA程序启动就会为这个程序分配固定的内存区域,虚拟机再根据内存区域做进一步的划分以保障程序的运行更加高效持久。

  • 方法区:​ 对于JVM的方法区也可以称之为永久区,它储存的是已经被java虚拟机加载的类信息、常量、静态变量;Jdk1.8以后取消了方法区这个概念,称之为元空间(MetaSpace)
  • 堆:​ 对象的实例以及数组的内存都是要在堆上进行分配的,堆是线程共享的一块区域,用来存放对象实例,也是垃圾回收(GC)的主要区域,​ 堆细分又分新生代、老年代,对于新生代又分为:Eden区和Surviver1和Surviver2区
  • 虚拟机栈:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量,存储指向对象实例的指针信息,每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
  • 本地方法栈:与虚拟机栈的功能基本是一致的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
  • 程序计数器:指的是存放下一条指令的位置的一个指针。它是一块较小的内存空间,且是线程私有的。由于线程的切换,CPU在执行的过程中,需要记住原线程的下一条指令的位置,所以每一个线程都需要有自己的程序计数器

2. 哪些内存区域是线程共享,哪些是线程私有的?

方法区和堆是线程共享的,虚拟机栈、本地方法栈、程序计数器是线程私有的,其生命周期跟随线程的生命周期开始和结束,线程共享的跟随JVM的生命周期开始与技术。在这里插入图片描述

3. 简述下JVM类加载过程?

整体过程:加载、验证、准备、解析、初始化,不同阶段主要做了哪些事情?一句话总结这个过程其实就是将Class文件从文件中加载并转换为JVM虚拟机的内部内存结构的过程,具体的执行过程如下:

  • 加载:对于虚拟机首先需要知道去哪里加载我们的Class文件,并将Class文件对应的结构转换成方法区运行时的数据结构
  • 验证:确保加载进入JVM的Class类是符合JAVA虚拟机规范的,不存在违反虚拟机安全的逻辑行为
  • 准备:​ 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。将对象初始化为“零”值
  • 解析:​ 解析阶段时虚拟机将常量池内的符号引用替换为直接引用的过程。
  • 初始化:​ 初始化阶段时加载过程的最后一步,而这一阶段也是真正意义上开始执行类中定义的Java程序代码
    在这里插入图片描述

4. 双亲委派机制具体流程?

每⼀个类都有⼀个对应它的类加载器。系统中的 ClassLoder 在协同⼯作的时候会默认使⽤双亲委派模型 。即在类加载的时候,系统会⾸先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,⾸先会把该请求委派该⽗类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当⽗类加载器⽆法处理时,才由⾃⼰来处理。当⽗类加载器为null时,会使⽤启动类加载器 BootstrapClassLoader 作为⽗类加载器。
在这里插入图片描述
​ 此机制保证JDK核心类的优先加载;使得Java程序的稳定运⾏,可以避免类的重复加载,也保证了 Java 的核⼼ API 不被篡改。如果不⽤没有使⽤双亲委派模型,⽽是每个类加载器加载⾃⼰的话就会出现⼀些问题,⽐如我们编写⼀个称为 java.lang.Object 类的话,那么程序运⾏的时候,系统就会出现多个不同的Object 类。

5. 如何识别已分配内存的对象什么时候回收?

相比较C++的程序需要在编码过程中手动管理内存,在JAVA程序的编码中是不需要手动管理内存而是由JVM虚拟机来自动管理,那么对于JAVA应用程序如何管理呢? JAVA的对象实例主要分配在堆上,主要存在下面两种方式来识别对象是否需要回收:

  • 引用计数法:​ 给对象添加一个引用计数器,每当由一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,任何时刻计数器为0的对象就是不可能再被使用的,可以被回收。
    • 优点:实现简单,判定效率也很高
    • 缺点:对象之间相互循环引用的问题无法解决
  • 可达性分析法:​ 通过一系列的成为“GC Roots”(活动线程相关的各种引用,虚拟机栈帧引用,静态变量引用,JNI引用)的对象作为起始点,从这些节点ReferenceChains开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC ROOTS没有任何引用链相连时,则证明此对象时不可用的

6. 垃圾回收算法是否有具体了解过?

垃圾回收算法:复制算法、标记清除、标记整理

  • 复制算法:​ 将内存分为⼤⼩相同的两块,每次使⽤其中的⼀块。当这⼀块的内存使⽤完后,就将还存活的对象复制到另⼀块去,然后再把使⽤的空间⼀次清理掉。这样就使每次的内存回收都是对内存区间的⼀半进⾏回收;
    • ​ 优点:实现简单,内存效率高,不易产生碎片
    • ​ 缺点:可用内存降低了一半
  • 标记清除:​ 标记出所有需要回收的对象,在标记完成后统⼀回收所有被标记的对象
    • 缺点:效率低,标记清除后会产⽣⼤量不连续的碎⽚,需要预留空间给分配阶段的浮动垃圾
  • 标记整理:​ 标记过程仍然与“标记-清除”算法⼀样,再让所有存活的对象向⼀端移动,然后直接清理掉端边界以外的内存;解决了产生大量不连续碎片问题

当前虚拟机一般是采用什么什么垃圾回收算法来进行垃圾回收的呢?一般情况下是根据不同代的特点来选择垃圾回收算法,又称做分代回收,新生代一般选择复制回收算法,由于新生代的对象多数是朝生夕死,95%以上的对象会随着线程的单次访问结束而被回收。 而老年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进⾏垃圾收集,以提升整体的空间利用率。

7. 常用的虚拟机指令有哪些?

当部署在服务器上的程序出现问题的时候一般情况下我们应该如何处理呢? 在没有完备的可视化监控我们如何查看程序运行情况,了解程序是否存在死锁问题。这时候就需要我们通过虚拟机提供的命令工具来分析我们的程序运行情况,下面分别来介绍一下常用的命令:

  • jps:查看java进程及相关信息,帮助我们了解当前机器上运行的java程序
  • jstat:查看JVM运行时的状态信息,包括内存状态、垃圾回收
  • jinfo:查看JVM参数
  • jstack:查看JVM线程快照,jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环

8. 一般什么情况会造成内存泄露?

那什么是内存泄露呢?内存泄露一般是因为不断有新增对象长期不被回收导致老年代不断被挤压,最终导致FullGC,而FullGC会导致(Stop The World)全量线程阻塞程序卡顿,进而对业务造成严重影响;那虚拟机内存不是会自动回收的吗? 上文有介绍到,对于普通的对象一般随着线程请求结束不存在外部引用的情况下一般会被自动回收,那内存泄露一般会有哪几类场景呢?

  • 类的静态属性:除非主动删除,否则不会被垃圾回收器回收
  • 未关闭的资源:比如文件处理、数据库连接等使用后如果不主动关闭则会一直占用着内存不释放
  • ThreadLocal:ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,在线程未结束的情况下,线程的副本内容将永远不会被清除,尤其在使用线程池的情况下

9. CPU 100%的问题一般如何排查?

  1. 通过TOP命令可以查看到当前占用CPU最多的进程ID
    在这里插入图片描述
  2. 通过top -Hp PID查询进程中进程的CPU使用率,查询占用cpu最高的线程ID
top -Hp 2831

在这里插入图片描述

  1. 通过top -Hp PID查询进程中进程的CPU使用率,查询占用CPU最高的线程ID,而Jstack命令相关的结果堆栈信息里面通过16进制来存储线程ID,相关命令如下
printf %x 4191
  1. 通过Jstack命令查询当前线程的现场信息,查看当前线程具体在做什么
jstack 2831 |grep 105f
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

蒲春伟

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值