一问一答之JVM
说下JVM的内部组成与作用
- 执行引擎(java -> class)
- 类加载器(加载class)
- 运行时数据区域(加载到内存中指这里)
- 本地库接口(运行需要本地接口支持)
- 本地方法库(接口需要依赖本地方法库)
联系:编译器会将java代码转为class字节码,类加载器将字节码加载到内存中,放在方法区内,字节码是JVM的一套指令集规范,并不能作用于操作系统,需要由JVM的解释执行引擎翻译为CPU的底层指令,整个过程需要调用本地方法库来实现这个功能
简单来说就是堆栈方法区
和程序计数器
程序计数器
作用
- 读取指令,实现流程控制(选择、循环、顺序、异常处理)
- 多线程情况下,记录线程当前位置
- 唯一不会出现OOM的地方,随线程生命周期决定(不涉及对象内存分配,也不承载对象引用)
虚拟机栈
会出现两个错误。StackOverFlowError
和 OutOfMemoryError
- StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError错误。
- 每次调用方法都会创建栈帧(包括局部变量表、操作数栈、动态链接、返回地址)
- 用来保存局部变量、参加方法计算与返回
本地方法栈
- 使用native方法服务
- 也会出现上述两种错误
- native方法使用C语言编写
堆
- 内存中最大的一块。共享区域,存放
对象
和数组
,都会在这里分配内存
所有的对象都会在堆中分配吗?有个逃逸的概念。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。 - 是垃圾回收的主要区域
- 1.8以前,堆内存通常分为:新生代、老生代、永久代
- 1.8之后移除永久代,加入元空间,使用的直接内存(物理内存)
- 新生代又分为eden区和survivor区,survivor区分为from survivor和to survivor又叫s0,s1
方法区
- 放常量、静态变量,类加载信息,又被称为永久代。于1.8之后移除,用元空间代替
- 类信息包含版本、字段、方法、接口、父类等信息
元空间
- 1.8后出现,使用本地内存
运行时常量池
方法区的一部分,存放类信息和常量池表,当无法申请到内存时会OOM。字符串常量池在堆
直接内存
也会出现OOM的问题,受到本机总内存大小和处理器寻址空间的限制
JVM创建对象是怎么分配的
- 先放到eden区,如果满了,触发minor gc。将eden区的剩余对象放入s0
- 如果此时再次触发gc,如果没有回收就会进入s1
- 如果默认15次回收后进入老年代
- 当老年代内存不足会触发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
- system.gc()
- 老年代不足
- 1.7的永久代不足
说下JVM是怎么调优的
VisualVM:如下图所示
在短短的运行时间中,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的时候返回一个系统通知
扩展
更多内容可点击参照这篇文章