前言
在java的使用过程当中,我们会发现java的内存是自己释放的,并不像C、C++代码那样,每一块儿内存都需要程序员自己去维护,但是在如此便捷的同时可能也会出现很多问题,比如内存溢出,内存泄漏更不好排查了,所以今天的文章中,小白会带大家先了解java的内存区域的到底是怎样的,以及各个组件的作用是什么,让你一点一点翻越虚拟机内存管理这座大山。
正文
我们先来看一张图:
这张图就是一个java虚拟机运行时数据图,深色区域代表是线程共享的区域,java程序在运行的过程中会把他管理的内存划分为若干个不同的数据区域,每一块儿的数据区域所负责的功能都是不同的,他们也有不同的创建时间和销毁时间,本文将会从这张图开始一一展开,清晰的告诉你每一个模块的作用。
1.程序计数器
程序计数器就像是控制城市交通的红绿灯一样,是整个系统的中枢。在jvm中,它就是程序控制流的指示器,循环,跳转,异常处理,线程的恢复等工作都需要依赖程序计数器去完成。
程序计数器是线程私有的,它的生命周期是和线程保持一致的,我们知道,N个核心数的CPU在同一时刻,最多有N个线程同时运行,在我们真实的使用过程中可能会创建很多线程,jvm的多线程其实是通过线程轮流切换,分配处理器执行时间来实现的。既然涉及的线程切换,所以每条线程必须有一个独立的程序计数器。
2.虚拟机栈
虚拟机栈,其描述的就是线程内存模型,也可以称作线程栈,也是每个线程私有的,生命周期与线程保持一致。在每个方法执行的时候,jvm都会同步创建一个栈帧去存储局部变量表,操作数栈,动态连接,方法出口等信息。一个方法的生命周期就贯彻了一个栈帧从入栈到出栈的全部过程。 局部变量表应该是我们接触的最多的,里面存储了java的8大基本数据类型(byte、short、char、int、float、long、double、boolean)、对象引用(reference类型,不是对象本身,是指向对象的引用)和returnAddress类型(指向一条字节码指令的地址)。局部变量表的存储单位是局部变量槽(slot),long和double类型会占据两个变量槽,其余类型只占用一个,但是每一个变量槽的大小是由jvm自己决定的。
3.本地方法栈
本地方法栈的概念很好理解,我们知道,java底层用了很多c的代码去实现,而其调用c端的方法上都会有native,代表本地方法服务,而本地方法栈就是为其服务的。
4.堆
堆可以说是jvm中最大的一块儿内存区域了,它是所有线程共享的,不管你是初学者还是资深开发,多少都会听说过堆,毕竟几乎所有的对象都会在堆中分配。
我们先从分配内存的角度看看堆是怎么样的:
其实这就是一个最真实的堆,可能有些同学会觉得我说的不对,应该还有新生代,老年代,永久代,伊甸区,servivor区等等。这种说法基于某种逻辑上说是对的,但是并不是标准,它只是某些垃圾回收器的设计理念,需要新生代,老年代收集器搭配才能工作。
我们来说说TLAB(thread local allocation buffer),TLAB的数量和线程数是一一对应的,也就是说,TLAB是线程私有的,在堆空间中分配,对象会首先存放在这个线程私有的TLAB中,可以提升线程分配的效率。
5.方法区
方法区也是所有线程共享的区域,它存储了被jvm加载的类型信息、常量、静态变量等数据。
运行时常量池就是方法区的一部分,编译期生成的各种字面量与符号引用就存储在其中。
6.直接内存
这部分数据并不是jvm运行时数据区的一部分,nio就会使用到直接内存,也可以说堆外内存,通常会配合虚引用一起去使用,就是为了资源释放,会将堆外内存开辟空间的信息存储到一个队列中,然后GC会去清理这部分空间。
堆外内存优势在 IO 操作上,对于网络 IO,使用 Socket 发送数据时,能够节省堆内存到堆外内存的数据拷贝,所以性能更高。看过 Netty 源码的同学应该了解,Netty 使用堆外内存池来实现零拷贝技术。对于磁盘 IO 时,也可以使用内存映射,来提升性能。另外,更重要的几乎不用考虑堆内存烦人的 GC 问题。但是既然是内存。也会受到本机总内存的限制
结语
今天和大家聊了聊java内存区域是怎样的,而这部分内容都是比较标准化得一个体现,并没有掺杂垃圾回收相关的知识,也没有掺杂jvm具体实现的相关逻辑,我们知道这是一个基础的架构,我们在开发中默认使用的jvm是hotspot,它是SunJDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机,他们都是基于jvm规范去开发的,所以了解规范之后再去学其他深入的实现,不要各个知识点紊乱的去学。
下期见,我是小白,记得关注点赞!!!
网友评论