JVM内置的检测工具:
一般使用如下几种:
jps: 返回当前操作系统中的所有JVM进程ID jps -l(输出包名.类名)
jmap:查看当前JVM的内存情况 jmap -heap PID(查看PID的JVM的堆情况)
jstack :查看当前JVM的线程栈情况,常用于解决线程卡死问题
Java内存模型
Java的内存模型主要定义JVM如何将变量存储到内存中,又如何将内存中的变量取回等细节。
此处的变量包括实例属性、静态属性以及数组元素但不包括局部变量和方法参数,因为后两者是线程私有的,不会被线程共享。
Java内存模型规定了:
- 所有变量必须都存储在主内存中
- 每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。
- 线程对于变量的所有操作(读取、赋值等)必须在工作内存中进行,而不能直接操作主内存。不同线程之间也无法做到访问彼此的工作内存,变量之间的值传递均通过主内存来实现。
Java内存模型的三大特性:
并发程序同时满足以下三个特性才是线程安全的,任意一个不满足都不是线程安全。
- 原子性:
即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。基本数据类型的访问读写是具备原子性的。如若需要更大范围的原子性,需要synchronized约束。 - 可见性:
可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。volatile、synchronized、final三个关键字可以实现可见性。
final:修饰的变量无法修改,所以其他线程看到的时候一定是最新的。
synchronized:一个时刻只有一个线程能访问。 - 有序性 :
如果在本线程内观察,所有的操作都是有序的;如果在线程中观察另外一个线程,所有的操作都是无序的。前半句是指"线程内表现为串行",后半句是指"指令重排序"和"工作内存与主内存同步延迟"现象。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before 原则。
volatile变量的特殊规则:
关键字volatile可以说是JVM提供的最轻量级的同步机制
1. 保证此变量对所有线程的可见性,这里的"可见性"是指 : 当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。普通变量做不到这一点,普通变量的值在线程间传递均需要通过主内存来完成。例如:线 程A修改一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写完成之后再从主内存进行读取操作,新值才会对线程B可见。
注:由于volatile关键字只保证可见性,在不符合以下两条规则的运算场景中,我们仍然需要通过加锁(synchronized或者lock)来保证原子性。
- 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变约束
因为Java里面的运算并非原子操作。
以num++这句代码为例,在多线程的场景下,实际上num++等同于num = num+1。volatile关键字保证了num的值在取值时是正确的,但是在执行num+1的时候其他线程可能已经把num值增大了,这样在+1后会把较小的数值同步回主内存之中。
2. 使用volatile变量的语义是禁止指令重排序。
volatile关键字禁止指令重排序有两层意思:
(1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
(2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序和程序代码中执行的顺序一致。
//x、y为非volatile变量
//flag为volatile变量
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到
语句1、语句2前面,也不会将语句3放到语句4、语句5后面。但是语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。