说下JVM的内部组成与作用

JVM

  • 执行引擎(java -> class)
  • 类加载器(加载class)
  • 运行时数据区域(加载到内存中指这里)
  • 本地库接口(运行需要本地接口支持)
  • 本地方法库(接口需要依赖本地方法库)

联系:编译器会将java代码转为class字节码,类加载器将字节码加载到内存中,放在方法区内,字节码是JVM的一套指令集规范,并不能作用于操作系统,需要由JVM的解释执行引擎翻译为CPU的底层指令,整个过程需要调用本地方法库来实现这个功能

jvm运行时区域

简单来说就是堆栈方法区程序计数器

程序计数器

作用

  1. 读取指令,实现流程控制(选择、循环、顺序、异常处理)
  2. 多线程情况下,记录线程当前位置
  3. 唯一不会出现OOM的地方,随线程生命周期决定(不涉及对象内存分配,也不承载对象引用)

虚拟机栈

会出现两个错误。StackOverFlowErrorOutOfMemoryError

  • StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError错误。
  • 每次调用方法都会创建栈帧(包括局部变量表、操作数栈、动态链接、返回地址)
  • 用来保存局部变量、参加方法计算与返回

本地方法栈

  • 使用native方法服务
  • 也会出现上述两种错误
  • native方法使用C语言编写

  • 内存中最大的一块。共享区域,存放对象数组,都会在这里分配内存
    所有的对象都会在堆中分配吗?有个逃逸的概念。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
    img
  • 是垃圾回收的主要区域
  • 1.8以前,堆内存通常分为:新生代、老生代、永久代
  • 1.8之后移除永久代,加入元空间,使用的直接内存(物理内存)
  • 新生代又分为eden区和survivor区,survivor区分为from survivor和to survivor又叫s0,s1

方法区

  • 放常量、静态变量,类加载信息,又被称为永久代。于1.8之后移除,用元空间代替
  • 类信息包含版本、字段、方法、接口、父类等信息

元空间

  • 1.8后出现,使用本地内存

运行时常量池

方法区的一部分,存放类信息和常量池表,当无法申请到内存时会OOM。字符串常量池在堆

直接内存

也会出现OOM的问题,受到本机总内存大小和处理器寻址空间的限制

JVM创建对象是怎么分配的

  1. 先放到eden区,如果满了,触发minor gc。将eden区的剩余对象放入s0
  2. 如果此时再次触发gc,如果没有回收就会进入s1
  3. 如果默认15次回收后进入老年代
  4. 当老年代内存不足会触发major gc,如果还是依然无法放入对象,则OOM

聊聊垃圾回收

  • 如何判断是否还在使用了?引用计数法可达性算法
  • 引用简单,有对象引用为+1,无对象为-1.但无法处理对象相互依赖的情况。
  • 可达性算法类似于树的结构,以GC root作为根节点一直往下搜,能够到达的地方都是存活的,不可达就是不可用的,就可以被回收了
  • GC root包含以下内容。栈引用,静态属性引用,常量引用

有哪些垃圾回收算法

标记-清除
将存活的标记,清理掉未标记的。缺点会造成大量不连续的内存碎片

标记-整理
将存活的挪到一边,将另一边清除。

标记-复制
将内存划为大小相同的两块,每次只使用其中的一块,将存活的放到另一块上,对第一块进行清理

一般分代收集指的是年轻代(标记-复制)和老年代(标记-清除/整理)

分代与分区收集的区别

当前主流采用分代收集。在新生代为什么用复制,因为对象死的多,成本低。老年代存活率高,且没有额外空间进行分配担保

CMS以最短停顿时间为目标,采用标记-清除算法。吞吐量低jdk9弃用,14删除

分区则将堆空间划分为连续不同的小区间,每块独立使用,独立回收,便于控制,且可以减少gc所产生的停顿

G1:高吞吐量,应用于多处理器和大容量的内存环境中

ZGC:jdk11退出的低延迟回收器,适用于大内存低延迟服务的内存管理和回收

说说各种gc

hotspot vm分为部分收集和全部收集(full gc)
新生代(minor gc)
老年代(major gc)

什么情况下会触发full gc

  1. system.gc()
  2. 老年代不足
  3. 1.7的永久代不足

说下JVM是怎么调优的

VisualVM:如下图所示
image-20230207200024438
在短短的运行时间中,Ede进行了49次GC,虽然时间短,但是能说明一个问题,新生代堆内存分配的空间太小,
导致频繁GC
同时,OId老年代也进行了33次GC,虽然运行时间也在不需要优化的范围内,而且从Survivor可以看出,基本没有GC,说明这些 都是大对象,直接进入到了Old老年代,导致GC频繁
所以,我们需要进行的优化就是加大新生代和老年代堆内存的大小,同时减少大对象的产生
可将VM参数改为:-Xms512m-Xmx512m-Xmn128m-X:+HeapDumpOnOutOfMemoryError

参考链接 — https://mikechen.cc/15386.html

谈谈常用命令及作用

  • jps:显示系统中所有的虚拟机进程
  • jstat:收集虚拟机各方面的运行数据
  • jinfo:显示虚拟机配置信息
  • jmap:生成虚拟机的内存转储快照
  • jhat:分析堆内存转储快照,不推荐使用,消耗资源而且慢
  • jstack:显示线程堆栈快照

JVM调优的一些参数可以举例一些吗

-Xms2g:初始化堆大小为 2g;

-Xmx2g:堆最大内存为 2g;

-Xmn1g:新生代内存大小为1g;-XX:NewSize 新生代大小,-XX:MaxNewSize 新生代最大值,-Xmn 则是相当于同时配置 -XX:NewSize 和 -XX:MaxNewSize 为一样的值;

-XX:NewRatio=2:设置新生代的和老年代的内存比例为 1:2,即新生代占堆内存的1/3,老年代占堆内存的2/3;

-XX:SurvivorRatio=8:设置新生代Eden和两个Survivor比例为 8:1:1;

–XX:+UseParNewGC:对新生代使用并行垃圾回收器。

-XX:+UseParallelOldGC:对老年代并行垃圾回收器。

-XX:+UseConcMarkSweepGC:以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。

-XX:+PrintGC:开启打印gc信息;

-XX:+PrintGCDetails:打印gc详细信息。

java会内存泄漏吗?

java拥有GC机制,但还是会出现内存泄漏的情况,如果GC无法进行回收,那么就会导致泄漏

java对象引用类型有哪些?具体使用场景是什么?

  • 强引用:new出来的就是,就算OOM也不会进行内存回收
  • 软引用:发生内存溢出前会被回收。通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂存缓存,当内存不足时清除掉,这样就可以保证使用缓存的同时,不会耗尽内存
  • 弱引用:gc的时候就会被回收,同样可用于内存敏感的缓存
  • 虚引用:无法通过虚引用获得对象,作用在于在GC的时候返回一个系统通知

扩展

更多内容可点击参照这篇文章