JVM运行时数据区
一个JVM进程也就是Java进程的内存情况。一个Java进程会有多个线程
安装Java环境变量的时候,会有JDK JRE,而JRE也就是Java运行时环境,也就是JVM
内存分为线程共享和线程独占的内存区域。
- 每个线程都可以访问的区域称之为线程共享区域
- 每个线程都会有它独立的空间,随线程生命周期而创建与销毁
如何开启一个新的t1线程,会开辟一片独占的内存区域,这块内存区域在逻辑上就代表了该线程。当线程启动的时候,这块内存区域会被分配,一旦线程销毁这块内存区域就会被回收。生命周期与线程相同。
1. 线程共享区域
线程共享部分有:方法区 堆内存
1.1 方法区
作用:存储加载的类信息、常量、静态变量、JIT编译后的代码数据
加载进方法区的类的类信息,是通过类对象的形式对外提供访问方式,如开发中拿一个类对象
是通过student.class
的方式拿到Student 类
的类对象,而这个类对象实际上是一个引用,而这个引用最终指向方法区类对象。
GC:方法区存在垃圾回收,但回收效率低。主要原因是类对象很难被回收(只要有类对象的引用,该类对象就不能被回收)
1.2 堆内存
作用:唯一目的存放对象实例,几乎所有的对象、数组都存放于此。
对于大多数应用而言,堆是JVM管理的内存中最大的一块内存区域,也是最容易OOM的区域。
大多数JVM都将堆实现为大小可扩展的,(通过 -Xmx 、-Xms控制)
问:堆中存储了对象,到底存了什么?
答:堆中只存储了对象的实例字段的值,其他类信息 以类结构信息存在于方法区。
问:如何界定一个变量存在于栈还是堆?
答:如果是局部变量(方法内部申明的变量),存在于栈内存(并且是在方法被调用之后才会存在于栈内存);如果是成员变量(类的成员变量),则存在于堆内存。
问:对象何时被回收?也就是如何界定对象是否 “存活”
答:当一个对象没有被引用了时,
- 可以通过
引用计数器算法
来判断是否被引用。存在两个对象相互引用的问题
可达性分析算法
:主流的商用程序语言(Java、C#)都是通过可达性分析算法来判断对象是否存活。
从GC roots
开始分析,如能够通过引用找到该对象,那么这个对象是可达的。如上图中的object4
可以通过object1--> object3--> object4
找到,object2
可以通过object1--> object2
找到。而object6
无论从哪个GC roots
都无法分析,找不到,即便是object5 object6 object7
三个对象相互引用,任何一个GC roots
都无法找到他们,那么他们皆不可达,就会被回收。
GC roots :可以理解为非常难以被回收的东东。可以是:1.虚拟机 ; 2、方法中静态属性引用的对象 ; 3、方法区中常量引用的对象; 4、Native方法引用的对象
2. 线程独占区域
2.1 虚拟机栈
虚拟机栈:线程中方法执行的模型,每个方法执行时,就会在虚拟机栈中创建一个栈帧,每个方法从调用到执行的过程,就对应着栈帧在虚拟机栈中从入栈出栈的过程。描述方法先进后出的调用。
栈帧:代表着一个方法
有几个线程,就会有几个虚拟机栈。
问:一个数据存在于栈中,线程是否安全
答:线程安全,因为一个虚拟机栈是线程独占的内存区域。
问:什么是java线程?
答:需要执行的一段代码流,先进后出
虚拟机栈,是为了执行java方法。
2.2 本地方法栈
虚拟机栈,是为了执行java方法;而本地方法栈是为了执行native方法。
2.3 直接内存
并非属于JVM内存区域,开发人员自己分配 / 回收内存。
Java程序运行过程
本地变量表,操作数栈
程序计数器
程序计数器通俗来讲就是字节码指令的地址,cpu在线程之间切换到时候,方便告诉cpu上下文,线程执行到那个指令了。
Java中的多线程是如何实现的?cpu上下文在线程之间 来回切换,执行指令。
线程安全问题
线程的内存划分:线程共享区域,线程独占区域。而线程共享区域存在线程安全问题
当多个线程出现共享资源争抢的时候,会出现线程安全问题。如:进程外的资源,如共享File文件的读写,DB数据的读写,一般指的是多个进程对文件/数据进行修改,而这些资源称为进程外的资源。
进程内的共享资源,也就是jvm共享区域,所有的线程都可以访问,当多个线程访问共享区域的时候,就会存在线程安全问题。
问:一个类的静态字段,多个线程并发的访问该字段,是否存在线程安全问题?
答:不安全。