什么是java虚拟机 (JVM)?

jvm全称-java Virtual Machine 翻译过来也就是java虚拟机的意思,大家都知道java是一门跨平台性很强的语言,而铸造java这一特性的最大功臣便是java的jvm,java程序的整体执行流程如下Java虚拟机 - 图1
从图上可以看出来,java的执行并不是直接编译成二进制而是变成成字节码文件,什么是字节码呢?我们可以理解为只有jvm能理解的运行的编码,这便是和C语言最大的不同之处,java编译后的字节码文件必须要依赖jvm虚拟机进行解释才能运行,这也就告诉我们了为什么java具有那么好的跨平台的能力,所以也可以这样理解jvm基本上是一切java程序的基石

  1. 垃圾回收机制
  2. 堆和栈的区别
  3. JVM的内存分区?常量和变量分别存放在哪?
  4. 堆是如何划分的?
  5. 常见JVM内存溢出错误有哪些?怎么设置相关JVM参数。

1.垃圾回收机制

大家都知道程序运行的时候是需要使用内存的,而内存又是有限的宝贵资源,程序在执行完代码片段后,需要释放不在需要的内存空间,腾出空间给后续的代码运行,传统C等语言都是程序员自行分配释放资源的,比如某一个代码片段程序员需要先调用构造函数分配内存给对象,在业务代码执行完后再调用析构函数去释放这个对象。java则把这个任务交个java虚拟机JVM去执行,不需要人物操作
Java虚拟机 - 图2

从中可以看出java里面GC的线程全程跟踪着代码的运行,并自身能触发垃圾回收,GC由jvm提供,是一种守护线程,负责监控并触发垃圾回收GC全程异步执行

2.堆和栈的区别

经常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“堆”笔者在后面会专门讲述,而所指的“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中局部变量表部分。
Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换[插图]优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

3.JVM的内存分区?常量和变量分别存放在哪?

JAVA虚拟机在执行java程序的时候会把他所管理的内存划分为若干个不同的数据区域。
图片.png

分区为:程序计数器、方法区、java虚拟机栈、本地方法栈、堆。
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

4.堆是如何划分的?

从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、ToSurvivor空间等。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread LocalAllocation Buffer,TLAB)。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

5.常见JVM内存溢出错误有哪些?怎么设置相关JVM参数。

1,堆内存溢出:异常信息会提示java.lang.OutOfMemoryError,随后会紧跟heap space信息。
可能是堆里面的对象太多,堆无法放下那么多对象实例。
也可能是有资源没有来得及及时回收,比如某些大型对象没有调用close()方法。
解决这种内存溢出的方法:
1)如果可以调大jvm内存,通过调整-Xmx=1024M -Xms=512M的参数调大虚拟机内存,看看内存是否还会溢出;
2)如果上述方法不行,则需要进一步分析内存溢出的原因,可能需要通过内存映像分析工具,分析dump出来的堆转储快照,分析到底是内存溢出还是内存泄露。内存泄露是指无用对象无法被回收,比如某个对象没有调用close方法。或者某个对象一直保持着引用,没有及时释放。内存溢出就是单纯地堆空间不够存放有用的对象。
如果是内存溢出,则使用方法1);如果是内存泄露,则需要使用分析堆转储快照分析出泄露对象到gc roots的引用链找出相应的对象。

2,方法栈溢出:异常信息会提示java.lang.StackOverFlowError.
一般是方法递归的深度太大导致。
这个解决就比较简单,找出方法并修改至合理即可。
单线程一般都只会抛出java.lang.StackOverFlowError,如果是多线程情况下,线程多了栈的请求深度仍然那么大,就可能导致java.lang.OutOfMemoryError,这种时候可以调小堆的大小,和栈的容量(深度)来解决。

3,方法区溢出:因为方法区本身就是堆的一部分,所以提示信息和堆溢出一样。
如果是这个区域产生了溢出,可能是由于加载的类信息太多,要判断哪些类是无用的比较困难,一般只能通过方法区的容量来解决。使用参数:
-XX:PermSize=20M -XX:MaxPermSize=30M来调整,如果是常量池溢出,也只能通过调整方法区来间接调整常量池大小。

4,本机直接内存溢出:异常信息提示java.lang.OutOfMemoryError
这种异常产生之后堆转储快照里面不会有明显的异常,如果代码中使用了nio,就可能会产生这种异常,一般从这个思路去解决。