JMM架构图
JMM的概念
Jmm本身是一种抽象的概念,并不真实存在,它描述的是一组规则与规范,通过这组规范定义了程序中各个变量的访问方式
程序计数器
程序计数器用来记录当前java程序执行的字节码行号,其为一个逻辑地址
为什么会引入程序计数器
因为在多线程环境下,同一确定时刻,cpu只会执行某一线程中的一条指令,程序计数器就是用来标识指令位置,保证cpu线程切换后程序可以正常执行
程序计数器的作用
- 记录当前线程所执行的字节码行号
- 改变计数器的值来选取下一条要执行的指令
- 与线程一对一,线程私有
- 若执行的是java方法,则记录jvm字节码的地址,若为Native方法,则记录undefined
- 不会发生内存泄漏
虚拟机栈
其描述的是java方法执行的内存模型,存储的单元是栈帧
虚拟机栈的结构如下图所示
什么是栈帧?
其为每一个java方法的内存模型,由局部变量表、操作栈、动态链接、返回地址组成
局部变量表与操作数栈
- 局部变量表:包含方法执行过程中的所有变量
- 操作数栈:入栈、出栈、复制、交换、产生消费变量
模拟一个方法执行过程jvm栈的处理
JVM虚拟机栈可能报出的异常有2种
- StackOverflowError
- OutOfMemoryError
StackOverflowError
产生的原因是jvm虚拟机栈的深度是固定的,线程请求深度超过限制,最常见的为递归调用
//递归调用实现FiboNaqie数列
package com.bdcloud.controller;
public class FiboNq {
public static void main(String[] args) {
System.out.println(FiboNq(0));
System.out.println(FiboNq(1));
System.out.println(FiboNq(2));
System.out.println(FiboNq(9));
System.out.println(FiboNq(1000000));//当执行该行的时候会出现StackOverflowError
}
public static int FiboNq(int n){
if (n == 0) return 0;
if (n == 1) return 1;
return FiboNq(n-1)+FiboNq(n-2);
}
}
该行函数执行的时候每次递归调用都会创建一个栈帧插入jvm栈中,若请求jvm栈的深度超过限定深度,则会报StackOverflowError
OutOfMemoryError
//执行以下代码则会产生outOfMemeryError
while (true){
new Thread(){
@Override
public void run() {
while (true){
}
}
}.start();
}
本地方法栈
本地方法栈与虚拟机栈的作用很相似,它用于调用navtive方法,是navitve方法的内存模型,navtive方法由c/c++编写
元空间
用来存储类加载的信息,如field与method等,其占用本机内存,不占用jvm内存,老版本的JMM中还有老年代,其作用与元空间一致,但是占用jvm内存,且包含常量池
java堆
- 对象实例的分配区域
- GC主要回收的区域
- 常量池
JVM三大性能调优参数-Xms、-Xmx、-Xss
- -Xms:初始化堆的大小
- -Xmx:可扩展堆的最大空间
- -Xss:每个线程虚拟机栈的大小。一般为256k
JMM中堆栈的区别
- 管理方式: 栈自动释放、堆需要GC
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片少
- 分配方式:栈支持静态分配与动态分配,堆仅支持动态分配
- 效率:栈的效率会高于堆的执行效率
String类的intern()方法
作用,将该String对象存入字符串常量池中,jdk1.6版本之前,只可以存入数据副本,但是1.7之后可以存入引用,demo如下
package com.bdcloud.controller;
public class StringIntern {
public static void main(String[] args) {
String s1 = new String("a");
s1.intern();
String s2= "a";
System.out.println(s1==s2);
String s3 = new String("a")+new String("a");
//1.6之后可以将引用存入字符串常量池
s3.intern();
String s4="aa";
System.out.println(s3==s4);
}
}