JVM基本概念

Jvm是java虚拟机,主要是辨认.class文件。而且能够解析它的指令,终究去调用操作系统上的函数。 运用javac将java程序编译成.class文件后,是需求java命令去履行它,可是操作系统并不认识,所以需求jvm去进行翻译.class文件。

java-JVM内存管理深度剖析
有了jvm,java就能够完结跨渠道。在不同操作系统上,咱们就需求提供不同的jdk渠道版别

跨言语:jvm只辨认.class文件 所以不同言语只需编译成.class文件就能够完结跨言语。

JVM、JRE、JDK的关系

JVM:仅仅将.class文件翻译成机器能辨认的机器码,假如咱们编写代码 那就需求jre,它包含jvm,同时包含许多类库,这样就组成了运转时环境。而jdk是编译代码、调试代码、反编译代码等等 如javac ,所以是jdk包含jre jre包含jvm.

java-JVM内存管理深度剖析

java-JVM内存管理深度剖析

JVM整体

一个 Java 程序,首先经过 javac 编译成 .class 文件,然后 JVM 将其加载到办法区,履行引擎将会履行这些字节码。履行时,会翻译成操作系统相关的函数

咱们所说的 JVM,狭义上指的就 HotSpot(由于JVM有许多版别,可是运用最多的是HotSpot)

运转时数据区域

java 引以为豪的便是它的自动内存办理机制。在 Java 中,JVM 内存主要分为堆、程序计数器、办法区、虚拟机栈和本地办法栈

java-JVM内存管理深度剖析

虚拟机栈

程序计数器: 很小一块内存空间,基本上是int型巨细,所以基本上是不会呈现oom的,毕竟程序计数器是存活于线程。当时线程履行的字节码的行号指示器,效果是 当履行的线程数量超过 CPU 核数时,线程之间会依据时刻片轮询抢夺 CPU 资源。假如一个线程的时刻片用完了,或者是其它原因导致这个线程的 CPU 资源被提早抢夺,那么这个退出的线程就需求单独的一个程序计数器,来记录下一条运转的指令。

虚拟机栈: 先进后出(FILO)的数据结构。虚拟机栈在JVM运转过程中存储当时线程运转办法所需的数据,指令、回来地址。Java 虚拟机栈是根据线程的。哪怕你只要一个 main() 办法,也是以线程的办法运转的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程相同的。 栈里的每条数据,便是栈帧。在每个 Java 办法被调用的时分,都会创立一个栈帧,并入栈。一旦完结相应的调用,则出栈。一切的栈帧都出栈后,线程也就结束了。

每个栈帧,都包含四个区域:(局部变量表、操作数栈、动态衔接、回来地址)

栈的巨细缺省为1M,可用参数 –Xss调整巨细,例如-Xss256k

局部变量表: 望文生义便是局部变量的表,用于寄存咱们的局部变量的。主要寄存咱们的Java的八大根底数据类型和目标的引证地址。

操作数据栈:寄存咱们办法履行的操作数的,它便是一个栈,先进后出的栈结构,操作数栈,便是用来操作的,操作的的元素能够是恣意的java数据类型

动态衔接: Java言语特性多态(需求类运转时才能确认详细的办法)。

回来地址: 正常回来(调用程序计数器中的地址作为回来)、反常的话(经过反常处理器表<非栈帧中的>来确认)

字节码助记码解说地址:cloud.tencent.com/developer/a…

java-JVM内存管理深度剖析

本地办法栈

本地办法栈跟 Java 虚拟机栈的功用相似,Java 虚拟机栈用于办理 Java 函数的调用,而本地办法栈则用于办理本地办法的调用。但本地办法并不是用 Java 完结的,而是由 C 言语完结的。

本地办法栈是和虚拟机栈非常相似的一个区域,它服务的目标是 native 办法。你甚至能够认为虚拟机栈和本地办法栈是同一个区域。

虚拟机标准无强制规定,各版别虚拟机自在完结 ,HotSpot直接把本地办法栈和虚拟机栈合二为一 。

线程同享的区域

办法区

办法区主要是用来寄存已被虚拟机加载的类相关信息,包含类信息、静态变量、常量、运转时常量池、字符串常量池。

JVM 在履行某个类的时分,有必要先加载。在加载类(加载、验证、准备、解析、初始化)的时分,JVM 会先加载 class 文件,而在 class 文件中除了有类的版别、字段、办法和接口等描绘信息外,还有一项信息是常量池 (Constant Pool Table),用于寄存编译期间生成的各种字面量和符号引证。

办法区与堆空间相似,也是一个同享内存区,所以办法区是线程同享的。假如两个线程都企图访问办法区中的同一个类信息,而这个类还没有装入 JVM,那么此刻就只允许一个线程去加载它,另一个线程有必要等待。在 HotSpot 虚拟机、Java7 版别中现已将永久代的静态变量和运转时常量池转移到了堆中,其余部分则存储在 JVM 的非堆内存中,而 Java8 版别现已将办法区中完结的永久代去掉了,并用元空间(class metadata)替代了之前的永久代,而且元空间的存储方位是本地

元空间巨细参数:****

jdk1.7及以前(初始和最大值):-XX:PermSize;-XX:MaxPermSize;

jdk1.8以后(初始和最大值):-XX:MetaspaceSize; -XX:MaxMetaspaceSize

jdk1.8以后巨细就只受本机总内存的限制(假如不设置参数的话)

JVM参数参阅:docs.oracle.com/javase/8/do…

Java8 为什么运用元空间替代永久代,这样做有什么好处呢?

官方给出的解说是:

移除永久代是为了交融 HotSpot JVM 与 JRockit VM 而做出的尽力,由于 JRockit 没有永久代,所以不需求装备永久代。

永久代内存常常不够用或发生内存溢出,抛出反常 java.lang.OutOfMemoryError: PermGen。这是由于在 JDK1.7 版别中,指定的 PermGen 区巨细为 8M,由于 PermGen 中类的元数据信息在每次 FullGC 的时分都或许被搜集,收回率都偏低,成果很难令人满意;还有,为 PermGen 分配多大的空间很难确认,PermSize 的巨细依赖于许多因素,比如,JVM 加载的 class 总数、常量池的巨细和办法的巨细等。

堆是 JVM 上最大的内存区域,咱们请求的几乎一切的目标,都是在这里存储的。咱们常说的废物收回,操作的目标便是堆。

堆空间一般是程序发动时,就请求了,可是并不一定会悉数运用。

跟着目标的频繁创立,堆空间占用的越来越多,就需求不定期的对不再运用的目标进行收回。这个在 Java 中,就叫作 GC(Garbage Collection)。

那一个目标创立的时分,到底是在堆上分配,仍是在栈上分配呢?这和两个方面有关:目标的类型和在 Java 类中存在的方位。

Java 的目标能够分为基本数据类型和一般目标。

对于一般目标来说,JVM 会首先在堆上创立目标,然后在其他地方运用的其实是它的引证。比如,把这个引证保存在虚拟机栈的局部变量表中。

留意: 大部分目标都是分配在堆上,可是有些是分配在栈上,这就涉及到办法逃逸。 逃逸办法基本原理: 分析目标动态效果域,当一个目标在办法里面被界说后,它或许被外部办法所引证,例如作为调用参数传递到其他办法中,这种行为被称为办法逃逸 换句话说 便是只在办法体内运用的引证目标,就直接分配在栈上

栈上分配(Stack Allocations):假如能够确认一个目标不会逃逸出线程之外,能够让该目标在栈空间上进行分配,目标所占用的内存空间就会跟着栈帧出栈而毁掉。这样做的好处便是削减资源耗费,对于JVM来说,对废物目标进行符号以及收回过程,都会耗费许多的资源,利用栈来分配会削减JVM符号收回目标的数量,减轻收回压力。

堆巨细参数:****

-Xms:堆的最小值;

-Xmx:堆的最大值;

-Xmn:新生代的巨细;

-XX:NewSize;新生代最小值;

-XX:MaxNewSize:新生代最大值;

例如- Xmx256m

直接内存

不是虚拟机运转时数据区的一部分,也不是java虚拟机标准中界说的内存区域;假如运用了NIO,这块区域会被频繁运用,在java堆内能够用directByteBuffer目标直接引证并操作;

这块内存不受java堆巨细限制,但受本机总内存的限制,能够经过-XX:MaxDirectMemorySize来设置(默许与堆内存最大值相同),所以也会呈现OOM反常。

java-JVM内存管理深度剖析

从底层深化理解运转时数据区

当咱们经过 Java 运转以上代码时,JVM 的整个处理过程如下:

1. JVM 向操作系统请求内存,JVM 榜首步便是经过装备参数或者默许装备参数向操作系统请求内存空间。

2. JVM 获得内存空间后,会依据装备参数分配堆、栈以及办法区的内存巨细。

3. 完结上一个过程后, JVM 首先会履行构造器,编译器会在.java 文件被编译成.class 文件时,搜集一切类的初始化代码,包含静态变量赋值语句、静态代码块、静态办法,静态变量和常量放入办法区

4. 履行办法。发动 main 线程,履行 main 办法,开始履行榜首行代码。此刻堆内存中会创立一个 Teacher 目标,目标引证 student 就寄存在栈中。

java-JVM内存管理深度剖析

** 留意:栈的内存要远远小于堆内存**

内存溢出

栈溢出

参数:-Xss1m, 详细默许值需求查看官网:docs.oracle.com/javase/8/do…

HotSpot版别中栈的巨细是固定的,是不支持拓展的。

java.lang.StackOverflowError 一般的办法调用是很难呈现的,假如呈现了或许会是无限递归。

虚拟机栈带给咱们的启示:办法的履行由于要打包成栈桢,所以天然生成要比完结相同功用的循环慢,所以树的遍历算法中:递归和非递归(循环来完结)都有存在的含义。递归代码简洁,非递归代码复杂可是速度较快。

OutOfMemoryError:不断建立线程,JVM请求栈内存,机器没有足够的内存。(一般演示不出,演示出来机器也死了)

堆溢出

内存溢出:请求内存空间,超出最大堆内存空间。

假如是内存溢出,则经过 调大 -Xms,-Xmx参数。

假如不是内存泄漏,便是说内存中的目标却是都是有必要存活的,那么久应该查看JVM的堆参数设置,与机器的内存对比,看是否还有能够调整的空间,再从代码上查看是否存在某些目标生命周期过长、持有状况时刻过长、存储结构设计不合理等状况,尽量削减程序运转时的内存耗费。

办法区溢出

1 运转时常量池溢出

2 办法区中保存的 Class 目标没有被及时收回掉或者 Class 信息占用的内存超过了咱们装备。****


留意 Class 要被收回,条件比较严苛(仅仅是能够,不代表必然,由于还有一些参数能够进行操控):****

1、 该类一切的实例都现已被收回,也便是堆中不存在该类的任何实例。****

2、 加载该类的ClassLoader现已被收回。

3、 该类对应的java.lang.Class目标没有在任何地方被引证,无法在任何地方经过反射访问该类的办法。

java-JVM内存管理深度剖析

本机直接内存溢出

直接内存的容量能够经过MaxDirectMemorySize来设置(默许与堆内存最大值相同),所以也会呈现OOM反常;

由直接内存导致的内存溢出,一个比较显着的特征是在HeapDump文件中不会看见有什么显着的反常状况,假如发生了OOM,同时Dump文件很小,能够考虑要点排查下直接内存方面的原因。

虚拟机优化技术

编译优化技术——办法内联

办法内联的优化行为,便是把目标办法的代码原封不动的“仿制”到调用的办法中,避免真实的办法调用罢了。

栈的优化技术——栈帧之间数据的同享

在一般的模型中,两个不同的栈帧的内存区域是独立的,可是大部分的JVM在完结中会进行一些优化,使得两个栈帧呈现一部分重叠。(主要体现在办法中有参数传递的状况),让下面栈帧的操作数栈和上面栈帧的部分局部变量重叠在一起,这样做不但节省了一部分空间,愈加重要的是在进行办法调用时就能够直接公用一部分数据,无需进行额定的参数仿制传递了。