《深入理解Java虚拟机》内存模型、JMM模型、常用jvm参数

前言

这几天是返校日,收拾来收拾去回到学校去,一样是周末,今天又是一个人来到公司,不同的是今天比以往早,同时多了点不一样的感觉——压力。

确实是这样,压力在两方面吧,对于技术岗位的同学来说,秋招已经过半,厉害的学生从6月18,某某跳动开始秋招就拿到各种offer,到现在啥也没有,确实是自己能力不够或者说好难~另一方面是开学时,我感受到大四的身边同学朋友都和其他人不一样,开始认真复习:考研、找工作、投简历、实习。

做为千军万马过独木桥里面的一只小马,能做的就只有:不忘初心、一往无前,冲!

从JVM的内存模型说起

《深入理解java虚拟机》第二章 39-44

介绍了大致的JVM模型,copy一个图

《深入理解Java虚拟机》内存模型、JMM模型、常用jvm参数

一、程序计数器

很小的内存,可以看做当前线程执行的字节码的行号指示器。分支、跳转、循环、线程恢复都需要它。线程私有,唯一一个java虚拟机规范中没有可能出现OutOfMemoryError情况的区域(执行Native方法,计数器的值为空)

 

二、虚拟机栈:两种情况的异常

1、线程请求深度大于虚拟机允许的深度——>*Error;

2、虚拟机动态拓展,无法申请到足够的内存——>OutOfMemoryError

线程私有栈内存,每个线程在创建的时候,都会创建私有的虚拟机栈。虚拟机栈存储着栈帧,怎么理解呢?执行一个方法,就对应着一个栈帧从入栈到出栈的过程。

一个栈帧包括:局部变量表、操作数栈、动态链接、方法出口

局部变量表解释一下,里面可能是这几种东西:Java的八大基本类型、对象引用(指向对象起始地址的指针)、returnAddress(指向一个字节码指令的地址)

 

三、本地方法栈

同上,区别是:

虚拟机栈——》虚拟机执行java方法服务(字节码文件——>加载解析初始化)

本地方法栈——》执行的是native方法服务

 

四、Java堆

内存共享,大部分对象都在堆上分配内存


如果堆没有内存完成实例分配,也没办法拓展,会有OutOfMemoryError

内存大概是这样:

《深入理解Java虚拟机》内存模型、JMM模型、常用jvm参数

五、运行时常量池和方法区

内存共享,方法区放的是:类信息、常量、静态变量、即时编译器编译后的代码等数据

为何放一起?Java7以后运行时常量池就是方法区的一部分,当无法满足分配需求,就会抛出;OutOfMemoryError

 

六、直接内存

jdk1.4引入的基于通道与缓冲区的IO

存在的意义:提高工作效率

eg:native堆执行的时候需要函数库,这块的函数库可以直接分配堆外内存,然后通过一个存储在java堆的一个流对象引用这块内存操作,避免了native堆和java堆数据来回复制

 

这块的内存是在不是java虚拟机规范中定义,不会受到java堆大小的限制,但是内存,还是会受到本机的总内存影响,动态拓展有可能出现OutOfMenoryError。(注意,我们平时会设置-Xmx等信息,这些不会影响到直接内存,要设置直接内存可以从物理的和操作系统层面限制)

 

JMM模型

前面说到,堆、方法区常量池是线程共享;而程序计数器、栈和虚拟机栈是线程隔离。

那我们每个线程是如何工作的呢?

《深入理解Java虚拟机》内存模型、JMM模型、常用jvm参数

这块有一个很重要的知识点:MES缓存一致性协议

为什么要有这个协议,我们可以看到;对一个变量,在主内存有一部分,在每个线程里面也有一份副本,甚至这个过程可能涉及到多级缓存副本。

那么问题来了——

如何保证数据一致性,我们总不能修改一次变量加一个锁吧?这样效率太低了,因此就有了这个协议,在这个协议里,每个变量有四个状态:

Modified(M):已修改。说明变量已经被持有它的处理器修改,如果一个段处于该状态,那么它在其他处理器缓存中的拷贝马上会变成“失效”(Invalid)状态。另外,已修改缓存段如果被丢弃或标记为失效,那么先要把它的内容回写到内存中。

>Exclusive(E):独占。只能被一个处理器持有该状态,一旦一个处理器持有了该状态的缓存段,那其它处理器就不能同时持有它。如果其他处理器本就持有了同一缓存段,那么它会马上变成“失效”(Invalid)状态。

>Shared(S):共享。这种状态下的缓存段只能被读取,不能被写入。

>Invalid(I):失效。如果缓存段为该状态,就相当于它从来没被加载到过缓存中一样。

 

只有当缓存段处于Modified或Exclusive状态时,处理器才算真正“持有”它,才能对其发起写操作,如果它没有持有该缓存段,又想要对其发起写操作,就需要先向总线发出申请。

再详细:

每个线程通过总线嗅探机制感知其他线程对改变量的修改。

而对一个线程对变量的操作有很多种过程:

lock、unlock;

read、load;

use、assign;

store、write;

 

常用JVM命令

jps

(JVM Process Status tool,虚拟机进程状况工具)它的功能和 Linux 中的 ps 命令比较类似。

我自己再测试环境经常就jps查看进程然后就kill -9 <进程号>干掉进程号,你们千万别学我。

建议用 kill -2或者kill -15 

Kill-2:功能类似于Ctrl+C是程序在结束之前,能够保存相关数据,然后再退出。


Kill -15 默认的kill方式,相对于给一个信号。

系统会发送一个SIGTERM的信号给对应的程序。当程序接收到该signal后,将会发生以下的事情。

 大部分程序接收到SIGTERM信号后,会先释放自己的资源,然后在停止。但是也有程序可以在接受到信号量后,做一些其他的事情,并且这些事情是可以。如果程序正在等待IO,可能就不会立马做出相应。也就是说,SIGTERM多半是会被阻塞的、忽略。

jinfo

查看参数的,看到info就记得和参数有关系,我是这么记的。

jinfo -flags 45129

这个命令可以查看进程号45129的参数配置。

 

jmap

用于查询堆的快照信息。

jmap -heap 45129:这个命令直接就显示堆的信息信息。在当前出口

但是一般会和下面的jhat结合执行

 

jhat

启动一个 web 站点来分析 jmap 生成的快照文件。

jmap -dump :file = <路径>   进程号

jhap ***.dump

 

 

jstat

监控信息工具,jstat 常用的查询参数有:

-class,查询类加载器信息;
-compiler,JIT 相关信息;
-gc,GC 堆状态;
-gcnew,新生代统计信息;
-gcutil,GC 堆统计汇总信息。

 

jstack

容易和上面记混,应该是stack就是和栈有关。

所以该命令是:查看当前虚拟机的线程快照,用它可以排查线程的执行状况,例如排查死锁、死循环等问题。

打印一下当前线程的快照信息。