目录:
一、JVM内存模型
为了屏蔽各种硬件和操作系统对内存访问的差异,java定义了JVM内存模型。
主内存:所有变量都存储在主内存中。类似于运行时数据区的堆和方法区
工作内存:每个线程都拥有自己的工作内存,工作内存保存了主内存中的变量副本。
线程:线程对变量的操作,只能在工作内存中进行,不能够直接读写主内存。
不同线程间的变量访问,需要通过主内存完成。
java内存模型和java运行时数据区域的关系:主内存对应着java堆和方法区,工作内存对应着java栈。
volatile关键字,使得变量的更新在各个工作内存中都是实时可见的。在DCL的单例模式中有运用到。
二、JVM运行时数据区
运行时数据区主要包括五个部分:程序计数器,方法区,本地方法栈,虚拟机栈,堆
深色为线程公有,浅色为线程私有
各个部分是什么功能,简要总结要点:
(1)程序计数器(线程私有):程序计数器是一小块内存空间,线程私有。我们编写的程序在JVM中是以字节码的形式存在,JVM一行一行的执行,众所周知,在同一个cpu的同一个内核中,JVM实现多线程的方式是实际上是通过线程轮流切换的形式实现的。程序计数器就是为了解决在多线程这个问题,程序计数器,记录了当前JVM执行的字节码的行号,所以无论由于多线程情景下的线程回复,还是程序自身的分支、循环、跳转、异常处理,都可以用这个程序计数器,记录字节码执行的行号。JVM就准确的知道每个程序执行到了哪里了。
(2)虚拟机栈(线程私有):虚拟机栈就是所谓的栈内存,线程私有,与线程的生命周期相同,线程结束,它(栈帧)也亡了。“虚拟机栈描述的是方法执行的内存模型”,顾名思义,就是存储方法在执行过程中所需要的元素了。每个方法执行的时候都会在栈内存中创建一个栈帧,这个栈帧存储的是:局部变量表、操作数栈、动态链接、方法出口,等。调用方法的时候,其实就是对应着这个栈帧的进栈和出栈。
局部变量表,存储着程序“编译期间”可知的:基本数据类型,对象引用,方法出口。其中,对象的引用,可以是对象的直接地址,也可以是对象的句柄,甚至可以是任何其他与此对象相关的位置。
操作数栈,java虚拟机用于计算的临时数据区。
动态链接,在class中的常量中,有一部分符号引用时在程序运行时转化为直接引用的,这部分就是动态链接,还有一部分是在类加载的时候转化了。
方法出口,就是returnAddress
(3)本地方法栈(线程私有):本地方法栈和虚拟机栈类似,只不过是本地方法栈中执行的是Native方法,Native方法就是所谓的用其他编程语言实现的方法,例如c语言。
(4)java堆(线程共享):JVM所管理的内存中的最大的一块,线程共享,虚拟机启动时创建。此区域唯一用途——存储对象实例。所有的对象实例以及数组都在堆上分配的,是垃圾回收的主要区域。
(5)方法区(线程共享):用于存储类加载信息、常量、静态变量(static)、即时编译器编译后的代码等。
补充:运行时常量池:类加载后,将class文件中的常量池加载到了这里,字符串常量池就是存在这里。其中,class文件中的常量池存储的是编译期生成的各种字面量和符号引用,class文件还存储着类的版本、字段、方法、接口等描述信息。
三、java堆heap
heap区即堆内存,整个堆大小=年轻代大小 + 老年代大小。堆内存默认为物理内存的1/64(<1GB);默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制,可以通过MinHeapFreeRatio参数进行调整;默认空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制,可以通过MaxHeapFreeRatio参数进行调整。
- Eden Space(伊甸园)
- Survivor Space(幸存者区)
- Old Gen(老年代)
(1)Eden Space 伊甸园:对象被创建的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域。
(2)Survivor Space 幸存者区:用于保存在eden space内存区域中经过垃圾回收后没有被回收的对象。Survivor有两个,分别为To Survivor、 From Survivor,这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全部释放),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换,始终保证一个survivor是空的。
Eden Space和Survivor Space都属于新生代,新生代中执行的垃圾回收被称之为Minor GC(因为是对新生代进行垃圾回收,所以又被称为Young GC),每一次Young GC后留下来的对象age加1。
(3)Old Gen老年代,用于存放新生代中经过多次垃圾回收仍然存活的对象,也有可能是新生代分配不了内存的大对象会直接进入老年代。经过多次垃圾回收都没有被回收的对象,这些对象的年代已经足够old了,就会放入到老年代。
当老年代被放满的之后,虚拟机会进行垃圾回收,称之为Major GC。由于Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。
heap和非heap详解:https://blog.csdn.net/shiyong1949/article/details/52585256/
补充:
1、基本类型的成员变量,局部变量存在哪块内存区域?
class{
private int i;
}
java虚拟机栈是线程私有的,生命周期跟线程相同,每个方法调用的时候都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法调用的过程,就代表了一个栈帧在虚拟机栈中入栈到出栈的过程,当进入一个方法时,这个方法在栈中需要分配多大的内存都是完全确定的,方法运行时不会改变局部变量表的大小——《深入理解java虚拟机第二版》
很多java程序员一开始就被网上的一些教程所误导:基本数据类型放在栈中,数组和类的实例放在堆中。 这个说法不准确,事实上,如上面的全局变量i,存放在java堆中。因为它不是静态的变量,不会独立于类的实例而存在,而该类实例化之后,放在堆中,当然也包含了它的属性 i。
如果在方法中定义了int i = 0 ;则在局部变量表创建了两个对象:引用i和0。 这两个对象都是线程私有(安全)的。 比如定义了int[] is = new int[10]. 定义了两个对象,一个是is引用,放在局部变量表中,一个是长度为10的数组,放在堆中,这个数组,只能通过 is 来访问,方法结束后出栈,is 被销毁,根据java的根搜索算法,判断数组不可达,就将它销毁了。
2、方法区和永久代的关系?
https://www.jianshu.com/p/797ec081a4aa