18个JVM常见问题汇总!

101 阅读10分钟

关于我:我是山茶,一个专注于技术的菜鸟。你懂的越多,就懂得不懂的越多。关注➕我,一起了解更多知识。

image.png

写在前面

初学java时,你可能不知道什么事单例设计模式、工厂设计模式等23中设计模式,但是,你肯定知道的一件事,java很厉害,相较于其他语言,它能做到“一次编译,到处运行”。那你知道这个实现的原理是什么嘛?

1.一次编译,到处运行的原理

Java源代码的扩展名为.java,经过编译程序编译之后生成扩展名为.class的字节码。想要执行字节码文件,执行平台则需要要安装JVM,JVM会将字节码翻译为对应的计算机指令,即:0、1指令。下图是一个简略的Java运行逻辑图,其中所有的平台都已经安装有JVM,那么也就是Java代码可以在任意的平台执行。

注意: 不同执行平台安装JVM,其实都是一样的, 在进行Java安装时,就会安装JVM, 因为JDK 附带安装程序,另一方面,JRE 仅包含执行源代码的环境,而 JVM 捆绑在软件 JDK 和 JRE 中。

2.什么是JVM

2.1.什么是JVM

JVM的全称是Java Virtual Machine。既Java虚拟机,它是一个引擎,提供运行时环境驱动 Java 代码或应用程序。它将 Java 字节码转换为计算机机器语言。JVM 是 Java 运行环境 (JRE) 的一部分。它不能单独下载和安装。要安装 JVM,您需要安装 JRE。在许多其他编程语言中,编译器为特定系统生成机器代码。但是,Java 编译器则称为 JVM 虚拟机生成代码

2.2.JDK、JRE与JVM的区别

这里讲到了了JVM,那不得不提到他的几个孪生兄弟JDK、JRE

1) JDK、JRE简介

JDK 的英文全称是 Java Development Kit。JDK是用于制作程序和Java应用程序的软件开发环境。Java 开发人员可以在 Windows、macOS、Solaris 和 Linux 上使用,是一个跨平台编程语言。可以在同一台计算机上安装多个 JDK 版本。

JRE 的英文全称是 Java Runtime Environment。JRE 是一个旨在运行其他软件的软件。它包含类库、加载器类和 JVM。简单来说,如果你想运行 Java 程序,你需要 JRE。所有 JDK 版本都与 Java Runtime Environment 捆绑在一起,无需在 PC 单独下载和安装 JRE。JRE 的完整形式是 Java 运行时环境。

2)JVM、JRE、JDK三者关系与区别:

  • JDK = JRE + 开发调试诊断工具
  • JRE = JVM + Java 标准库
  • JDK是一个软件开发工具包,而JRE是一个允许Java程序运行的软件包,JVM则是一个执行字节码的环境。
  • JDK 是平台相关的,JRE 也是平台相关的,但JVM 不是平台相关的。

3.JVM常见问题有哪些

3.1.Java 字节码

Java字节码是指令集的的Java虚拟机(JVM)。全球百科

通俗的讲就是 Java 源代码编译后的中间代码格式,一般也称为字节码文件

1)字节码文件包含哪些内容

整个字节码文件本质上就是一张表

为了更好的理解,我们将其分为7个大类,分别为为:

  • 魔数与Class文件版本
  • 常量池
  • 访问标志
  • 类索引、父类索引、接口索引
  • 字段表集合
  • 方法表集合
  • 属性表集合

2)什么是常量

常量是指程序运行过程中值始终不变的量。常量的特点是一旦被定义就不能被修改或重新定义。一般在数学和物理中会存在很多常量,它们都是一个具体的数值或一个数学表达式。例如,数学中的圆周率π。就是一个常量,它的取值就是固定且不能被改变的

通常使用用final 修饰的成员变量表示常量,值一旦给定就无法改变!

final 修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量

2)什么是常量池

jvm虚拟内存分布图解,其中方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置

常量池分为两种:静态常量池运行时常量池

静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。其中有final字面量常量值,及类和接口的全限定名、字段名称和描述符、方法名称和描述符等符号引用量

运行时常量池, 则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

3.2.JVM 的运行时数据区有哪些

Java程序运行的过程中,JVM会将其所管理的内存划分成若干个区域,统称为是运行时数据区。 它包含了方法区、虚拟机栈、本地方法栈、堆、程序计数器等5个部分。

1)什么是堆内存

堆内存是指由程序代码自由分配的内存,与栈内存作区分。

在 Java 中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,所有线程都可以访问堆内存。

通常堆空间初始值:

通常由-Xms指定,默认是物理内存的1/64。电脑内存是16G,公式为:16*1024/64=256M

堆空间最大值:

通常由-Xmx指定,默认是物理内存的1/3。电脑内存是16G,堆空间最大值:16/4=4G
老年代 :

占用整个堆内存2/3的空间,假设堆内存是4G,老年代约为2730M
新生代:

通过以下参数设置

  • -XX:NewSize=256m : 设置新生代初始内存
  • -XX:MaxNewSize=256m : 设置新生代最大内存
  • -Xmn256m : 将NewSize与MaxNewSize设为一致。为256m

2)堆内存包括哪些部分

以 Hotspot 为例,堆内存(HEAP)主要由 GC 模块进行分配和管理,可分为以下部分:

  • 新生代
  • 存活区
  • 老年代

其中,新生代和存活区一般称为年轻代。其中在新生代中eden、from、to的比例为8:1:1,新生代默认占用整个堆内存1/3的空间,新生代与老年代所占比值为1:3。

注⚠️:在JDK1.8之后,将最初的永久带内存空间取消了,该图为JDK1.8之前的内存空间组成

3)什么是非堆内存

除堆内存之外,JVM 的内存池还包括非堆(NON_HEAP),对应于 JVM 规范中的方法区,常量池等部分:

  • MetaSpace
  • CodeCache
  • Compressed Class Space

3.3.什么是内存溢出

内存溢出也称为 out of memory,指在进行程序空间申请时,没有足够的内存用于分配,便会出现该报错,比如我们在申请一个long数据内存时,此时内存仅剩余一个int的类型内存大小,不足以分配,那就会出现内存溢出

1)什么是内存泄漏

与内存溢出则不同,内存泄漏则是,你向内存申请了一块空间,但是在使用完成以后该空间却不会归还给系统,结果造成了申请空间的丢失即不能访问,系统也不能再对其进行操作和分配。

2)内存泄漏、内存溢出有什么关系

内存泄漏的堆积最终会导致内存溢出,当不可用的空间逐渐变多,系统资源逐渐变少,会导致当有分配空间请求出现时,无空间可以分配,最终走向了内存溢出

3.4.常用的JVM启动参数有哪些

JVM 可配置参数已经达到 1000 多个,其中 GC 和内存配置相关的 JVM 参数就有 600 多个。但在大部分业务场景下,常用的 JVM 配置参数也就 控制在10 个左右,例如下面的参数配置

# 设置堆内存
-Xmx4g -Xms4g 
# 指定 GC 算法
-XX:+UseG1GC -XX:MaxGCPauseMillis=50 
# 指定 GC 并行线程数
-XX:ParallelGCThreads=4 
# 打印 GC 日志
-XX:+PrintGCDetails -XX:+PrintGCDateStamps 
# 指定 GC 日志文件
-Xloggc:gc.log 
# 指定 Meta 区的最大值
-XX:MaxMetaspaceSize=2g 
# 设置单个线程栈的大小
-Xss1m 
# 指定堆内存溢出时自动进行 Dump
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/usr/local/
# 指定默认的连接超时时间
-Dsun.net.client.defaultConnectTimeout=2000
-Dsun.net.client.defaultReadTimeout=2000
# 指定时区
-Duser.timezone=GMT+08 
# 设置默认的文件编码为 UTF-8
-Dfile.encoding=UTF-8 
# 指定随机数熵源(Entropy Source)
-Djava.security.egd=file:/dev/./urandom 

1)设置对内存XMX应该考虑哪些因素

需要根据具体系统的配置来决定,要给操作系统及JVM 本身留下一定空间。推荐配置系统或容器里可用内存的 70%左右时最好的。

2)怎样开启GC日志

JDK 8 及以下版本通过以下参数来开启 GC 日志:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

JDK 9 及以上的版本,则为以下格式类型:

-Xlog:gc*=info:file=gc.log:time:filecount=0

3.5.常见的垃圾收集器有哪些

常见的垃圾回收器有以下的7种,

ParNew收集器、Parallel Scavenge 收集器、Serial Old收集器、Parallel Old收集器、CMS 收集器、G1收集器、Serial收集器,每一种的使用方式和场景都有所不同,后续文章中会专门介绍相关内容,大体可以分为四类:串行垃圾收集器、并行垃圾收集器、CMS垃圾收集器和G1垃圾收集器。

1)什么是年轻代

年轻代是分来垃圾收集算法中的一个概念,相对于老年代而言,年轻代一般包括:

  • 新生代,Eden 区。
  • 存活区,执行年轻代 GC 时,用存活区来保存活下来的对象。存活区也是年轻代的一部分,但一般有 2 个存活区,所以可以来回倒腾。

2)什么GC停顿

因为 GC 过程中,有一部分操作需要等所有应用线程都到达安全点,暂停之后才能执行,这时候就叫做 GC 停顿,或者叫做 GC 暂停。

3.6.MinorGC和fullGC

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。 Major GC 是清理永久代。Full GC 是清理整个堆空间—包括年轻代和永久代。

FullGC是发生在老年代的垃圾收集动作,老年代一般是由标记清除算法或者是标记清除算法与标记整理算法的混合实现,因为老年代里面的对象几乎个个都是在 Survivor 区域中存活过来的,所以,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长

注意:老年代执行了FullGC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。且代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用),就会出现java.lang.OutOfMemoryError: Javaheap space异常,这时明显是堆内存不足所导致

4.小结

在日常的工作中,肯定会有大家遇到的jvm问题不在上述的jvm问题中,如果有,可以根据自身情况进行思考以及时间的答案发我,欢迎沟通,一起进步。