文章目录
- 程序计数器
- 栈
- 堆
- 方法区
- 知识延申 -- 字符串常量池
程序计数器
并发情况下,会发生线程之间的上下文切换,当 线程1 的CPU时间片用完后,需要程序计数器记录 线程1 的下一条JVM指令的地址,等下一次 线程1 继续运行的时,才能从正确的位置开始继续执行
程序计数器是线程私有的 , 既 一个线程计数器 记录一个 线程 的指令位置
而且程序计数器是运行时数据区域唯一一个不存在内存溢出的区域
栈
线程是什么?实际上可以理解为线程就是一个栈
线程调用方法 就是复制方法入栈的过程
方法入栈后就会形成栈帧
比如下面的一段代码
public class JvmDemo {public static void main(String[] args) {System.out.println("开始");func();System.out.println("结束");} public static void func(){}
}
执行该程序会创建一个主线程,然后main方法入栈,示意图如下
接着mian方法执行到 func(); ,就会复制一份 func 方法入栈,如下
func方法执行完后出栈,如下
最后mian方法再出栈
我们说线程可以简单的认为就是栈,所以也很明显,栈是线程私有的
栈的容量有限,如果我们不断调用方法入栈,就会导致栈溢出 ( 比如递归时没有递归出口 )
栈分为 虚拟机栈 和 本地方法栈
可以简单理解为 :
-入栈的方法由 java 语言编写 的为虚拟机栈
-入栈的方法由 C语言编写 的为本地方法栈
Object类中有很多本地方法,比如反射中用到的 public final native Class<?> getClass();
,添加了native 关键字,使用C语言编写,为了能直接对接操作系统
关于栈的几个问题:
- 垃圾回收是否涉及栈内存? 不涉及 ,因为栈帧执行完就会自动出栈,无需垃圾回收
- 栈内存是否分配越大越好吗?错误 ,物理内存大小一定,栈内存越大,能同时纯在的栈就会越少,既 线程数会越少
- 方法内的局部变量是否是线程安全的? 是线程安全的,线程调用方法是复制方法入栈,所以每个线程都有自己的方法副本,也就有了自己的局部变量副本,所以在操作时,不会受到其他线程的干扰
堆
堆 – 存储对象实例
比如下面代码
public class JvmDemo {public static void main(String[] args) {Person person = new Person();}
}class Person{}
其在内存中的模型如下
person引用存在main方法的栈帧中,而person实例对象存在于堆区中
堆区是所有线程共享的 ,比如
public class JvmDemo {public static void main(String[] args) {Person person = new Person();Thread thread01=new Thread(()->{Person person01 = new Person();});thread01.start();}
}class Person{}
则内存模型如下,为了突出重点,省略 thread01 引用和实例对象 在内存中的展示,以及 thread01.start(); 的调用过程
堆的内存有限,不能无限创建对象实例,否者会堆内存溢出
因此JVM存在堆区的垃圾回收机制,清除垃圾对象,垃圾对象 可以简单定义为没有引用指向的实例对象
比如
Person person = new Person;
person = new Person;
此时就没有引用指向第一个new出来的对象,那么它就会被垃圾回收
存在堆区中的对象实例是线程间共享的,对象中的 全局变量 需要考虑线程安全问题
比如
public class JvmDemo {public static void main(String[] args) throws InterruptedException {Person person = new Person();Thread thread01=new Thread(()->{for (int i=0;i<100000;i++){person.money++;}});thread01.start();for (int i=0;i<100000;i++){person.money++;}thread01.join();System.out.println(person.money);}
}
class Person{public int money=0;
}
输出的结果不一定是200000
方法区
方法区 – 存储 类信息 和 静态变量、方法
在 jdk 1.8之前,方法区存在于JVM内存中,其实现方法被称为 永久代;jdk 1.8及之后,方法区从JVM内存移出到本地内存,其实现方法被称为 元空间
方法区是线程共享的,因此多个线程修改同一个类的静态变量的时候同样存在线程安全问题,比如
public class JvmDemo {public static void main(String[] args) throws InterruptedException {Thread thread01=new Thread(()->{for (int i=0;i<100000;i++){Person.money++;}});thread01.start();for (int i=0;i<100000;i++){Person.money++;}thread01.join();System.out.println(Person.money);}
}
class Person{public static int money=0;
}
输出结果同样不一定是200000,此处建议先学习一下什么是静态变量
方法区同样存在内存溢出的问题
知识延申 – 字符串常量池
JVM中还有一个特殊又重要的区域,就是 字符串常量池(StringTable),可以参考我的另外一篇博客
【Java 基础】你真的会用 String 吗?