文章收录在网站:http://hardyfish.top/
文章收录在网站:http://hardyfish.top/
文章收录在网站:http://hardyfish.top/
文章收录在网站:http://hardyfish.top/
内存模型
内存结构
堆:
- 存放对象实例, 几乎所有的对象实例以及数组都在这里分配内存。
虚拟机栈:
- 栈帧:用于支持虚拟机进行方法执行的数据结构。
- 存储了方法的
局部变量表
, 操作数栈,动态连接和返回地址等地址。- 每个方法从调用到执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。
- 栈内存为线程私有的空间, 每个线程都会创建私有的栈没存, 生命周期与线程相同。
- 除了一些 Native方法调用是通过本地方法栈实现的, 其他所有Java方法调用都是栈来实现的。
本地方法栈:
和虚拟机栈非常相似,区别如下:
虚拟机栈执行 Java 方法服务。
本地方法栈使用到的 Native方法(例如 C++ 程序)服务。
方法区:
- 各个线程共享的内存区域。
- 存储已经被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
- 具体实现: 永久代,元空间。
永久代和元空间的区别:
JDK1.8之前是永久代。
JDK1.8之后是元空间。
存储位置:
- 永久代使用的内存区域是 JVM进程所使用的区域,大小受 JVM的大小限制。
- 元空间使用的是物理内存区域,元空间大小只受物理内存大小的限制。
存储内容不同:
- 永久代主要存储方法区存储内容中的数据。
- 元空间只存储类的元信息,而静态变量和运行时常量池都在堆中。
程序计数器:
- 一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
- 每条线程都有一个独立的程序计数器,各线程之间的计数器互不影响,为线程私有。
程序计数器的作用:
- 多线程的情况下,用来记录当前执行的位置,当线程切换回来时能正确运行。
- 字节码解释器通过改变程序计数器来以此读取指令,从而实现代码的流程控制: 如: 选择, 循环, 顺序执行, 异常处理等。
程序计数器是唯一不会出现
OutOfMemoryError
异常的内存区域,其生命周期随着线程的创建而生,线程的销毁为亡。
堆结构
基于分代收集理论设计,分为年轻代和老年代,其中年轻代又分为Eden区和Survivor 0区和Survivor 1区(Survivor0和Survivor1也叫From和To区)
- 默认情况下,老年代占总的堆内存的2/3,年轻代占1/3
什么E:S0:S1默认是8:1:1
新生代中有98%的对象是朝生夕灭的,每次MionrGC后存活的对象应该小于等于2%。
所以采用复制算法的新生代似乎可以不用将内存分成大小相等的两块了,但考虑到实验偏差以及实际情况的多样性。
默认预留了10%的内存用于存放存活对象。
堆内存大小
初始内存默认为电脑物理内存大小的
1/64
,最大内存默认为电脑物理内存的1/4
,但是堆空间的大小可以调节。
符号引用
在JVM中,一个类的方法名、类名、修饰符、返回值等等都是一系列的符号,而这些符号都是一个个的常量,存储在常量池中。
同时这些个符号被加载到内存后,都有对应的内存地址,而这些内存地址就是直接引用。
动态链接
符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。
另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。
方法出口
方法出口就是方法执行完成后,需要返回的一些信息。
一般来说,方法正常退出时,方法的PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。
而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息。
操作数栈
就是存放方法运行过程中产生的一些临时数据,目的是为了计算,以栈的数据结构进行存储。
局部变量表
方法内部的变量都是存放在局部变量表中。
如果是对象类型,局部变量表中存放的是堆给对象分配的内存地址,也就是指针,而不是对象直接存在局部变量表中。
元空间
在JDK1.7之前,HotSpot 虚拟机把方法区当成永久代(方法区的落地实现)来进行垃圾回收。
JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
HotSpots取消了永久代,那么是不是也就没有方法区了呢?
- 不是,方法区是一个规范,规范没变,它就一直在,只不过取代永久代的是元空间(Metaspace)而已。
它和永久代有什么不同的?
存储位置不同:
- 永久代在物理上是堆的一部分,和新生代、老年代的地址是连续的,而元空间属于本地内存。
存储内容不同:
- 在原来的永久代划分中,永久代用来存放类的元数据信息、静态变量以及常量池等。
- 现在类的元信息存储在元空间中,静态变量和常量池等并入堆中,相当于原来的永久代中的数据,被元空间和堆内存给瓜分了。
为什么要废弃永久代,引入元空间?
在原来的永久代划分中,永久代需要存放类的元数据、静态变量和常量等。
它的大小不容易确定,因为这其中有很多影响因素,比如类的总数,常量池的大小和方法数量等,
-XX:MaxPermSize
指定太小很容易造成永久代内存溢出。移除永久代是为融合HotSpot VM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
- 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
废除永久代的好处:
由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间,不会遇到永久代存在时的内存溢出错误。
将运行时常量池从PermGen分离出来,与类的元数据分开,提升类元数据的独立性。
将元数据从PermGen剥离出来到Metaspace,可以提升对元数据的管理同时提升GC效率。