目录
前文回顾
上一篇文章我们聊了在什么情况下会触发类的加载?加载之后的验证、准备和解析分别是干什么的?其中尤为重要的是准备阶段和初始化阶段,是如何为类分配内存空间的?然后类加载器的规则是什么?
一、存放类的方法区
这个方法区是在JDK 1.8以前的版本里,代表JVM中的一块区域,在JDK 1.8以后,这块区域的名字改了,叫做“Metaspace”。
方法区中存放的是类型信息、常量、静态变量、即时编译器编译后的代码缓存、域信息、方法信息等。
1.类型信息指加载的类型(类、接口、枚举、注解)
①这个类型的全局限定名
②这个类型直接父类的全局限定名(interface与Java.lang.Object没有父类)
③这个类型的访问权限修饰符
④这个类型直接接口的有序列表
二、执行代码指令用的程序计数器
程序计数器就是用来记录当前执行的字节码指令的位置的,也就是记录目前执行到了哪一条字节码指令。
注意,每个线程都会有自己的一个程序计数器,专门记录当前这个线程目前执行到了哪一条字节码指令了
三、Java虚拟机栈
Java虚拟机栈,是来保存每个方法内的局部变量等数据的,每个线程都有自己的Java虚拟机栈,
如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧,栈帧里有这个方法的局部变量表 、操作数栈、动态链接、方法出口等东西。
压栈与出栈
当一个栈帧对应的A方法中调用了另一个B方法,这个B方法里也有自己的局部变量,那么这个时候会给这个B方法又创建一个栈帧,压入线程的Java虚拟机栈里。
接着如果B方法执行完毕,B方法对应的栈帧就会从Java虚拟机栈中弹出。
四、Java堆内存
存放的是我们new出来的对象,声明的对象类型指向的就是堆中这个实例的地址。
五、流程图
六、其他内存区域
其实在JDK很多底层API里,比如IO相关的,NIO相关的,网络Socket相关的如果大家去看他内部的源码,会发现很多地方都不是Java代码了,而是走的native方法去调用本地操作系统里面的一些方法,可能调用的都是c语言写的方法,或者一些底层类库比如下面这样的:
public native int hashCode();
在调用这种native方法的时候,就会有线程对应的本地方法栈,这个里面也是跟Java虚拟机栈类似的,也是存放各种native方法的局部变量表之类的信息。还有一个区域,是不属于JVM的,通过NIO中的allocateDirect这种API,可以在Java堆外分配内存空间。然后,通过Java虚拟机里的DirectByteBuffer来引用和操作堆外内存空间。其实很多技术都会用这种方式,因为有一些场景下,堆外内存分配可以提升性能。
结尾
给大家留个思考题
在Java堆内存中分配的那些对象,到底会占用多少内存?一般怎么来计算和估算系统创建的对象对内存占用的压力呢?