JVM- 技术专题 - 深入理解内存结构
一、JDK、JRE、JVM关系
JDK、JRE、JVM的关系
可以看到他们关系是JDK>JRE>JVM
JDK:jdk是支持 JAVA程序开发的最小环境,集成了JRE和一些工具包,如 javac,jar等;比如一个可运行jar,你就需要安装了jdk,才能运行起来
JRE:Java运行时的标准环境,除了JVM的环境还有一些基本的JAVA库,比如界面的 swing、I/O等
JVM:熟称Java虚拟机,也叫运行时数据区域,是保证跨平台的基本,因为 jvm 只认识字节码,只要linux、window、mac 有jvm都是可以编译执行的;
线程共享区:程序运行时,数据在各个线程之间是共享的,某个方法,某个类,还有一些运行时常量。
线程私有区:各个线程之间的数据是独立的,比如多线程的数据。
二、线程私有区
2.1、程序计数器
首先先了解程序计算器,线程(UI线程)中程序语句的执行都离不开它,对它的解释如下:
1、是一块较小的内存,可以看做当前线程执行字节码时的行号指示器。
2、程序的运行,比如跳转、循环等指令,就是通过改变计数器的数值,来选取下一条需要执行的字节码指令。
3、多线程时,每个线程的程序计算器都是独立的,相互不干扰,独立储存;即记录每次线程的位置,方便下次线程切换过来,知道上次线程的运行到哪了。
2.2、虚拟机栈
虚拟机栈也是线程私有的,与线程的生命周期相同;它对应着线程的内存模式,每个方法在执行的时候,都有一个栈帧用于存储局部表,操作数栈、动态链接、方法出口等信息;每个方法的执行,都对应着一个栈帧在虚拟机栈中的入栈和出栈,如下图
局部变量表:
1、存储了编译器存放着各种基本数据类型(boolean、byte、char等)
2、对象引用类型,这里的对象不是对象本身,可能是对象的寻址指针,也可能是句柄或者相关位置
3、returnAddress 类型,指向了一条字节码指令的地址 (现在已经很少有虚拟机用了)
当进入一个方法时,这些变量在帧中分配的内存大小时固定的,在运行时不会改变局部变量表的大小。针对这个区域,规定了两种异常情况:
1、如果虚拟机不支持动态扩展,当线程请求的栈大小大于虚拟机规定的大小时,抛出 StackOverflowError
2、如果虚拟机栈可以动态扩展,如果扩展时,无法申请到足够的内存,抛出 OutOfMemoryError
操作数栈:
操作数栈,也可以称做操作栈,它可以是 Java 的任意类型,在数据提取时入栈和出栈,比如 int a = 1 + 2;它会先把 1,2 从栈中取出来,把它复制给 a 后,再把结果入栈。
动态连接:
这里需要先理解静态连接,比如类加载的解析步骤,是直接将符号引用转换为直接引用,称为静态解析;而动态连接,则是运行期间,把符号引用转换为直接引用。
可以这样理解符号引用:
比如有当前运行类D,D类中还有其他类的申明,比如C,但虚拟机只会加载当前类,所以C它会由一个符号引用来代替,当类在解析的时候,会把这个符号引用转换为直接引用,直接引用就是类C的指针地址;关于类加载,可以到深入Java虚拟机之 – 类加载机制细看。
方法返回地址:
每个方法运行结束,只有两种方法可以退出方式;一是正常返回数据,这种称为正常完成出口;二是遇到异常,也会导致退出,称为异常完成退出。
无论哪种退出,都需要返回到方法被调用的位置,程序才能继续进行。
2.3、本地方法栈
本地方法栈与虚拟机栈的作用非常相似;只不过虚拟机栈执行的是 java 的字节码服务,而本地方法栈执行的是 Native 方法服务;
本地方法栈同样会穿件栈帧,如局部变量表、操作栈等信息,同时也有 StackOverflowError 和 OutOfMemoryError 异常
三、线程共享区
3.1、Java堆:
是Java虚拟机锁管理的内存中最大的一块,在虚拟机启动创建时,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这分配内存的;
Java 堆是内存回收的主要区域,也叫 GC 堆;根据规定,Java堆的物理地址可以是不连续的,只要保证逻辑上是连续的即可。由于Java 堆基本采用分代手机算法,所以也可以分为:新生代和老年代;再细致分,也可以分为 Eden空间,From Survivor 空间、To Surivivor 空间等涉及到的GC回收算法
3.2 方法区
方法堆也是线程共享的一个区域块,它用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区规定为 Java 堆的一个逻辑模块,但它还有一个方法,叫 Non-Heap (非堆) ,目的就是为了和 Java堆区分开来
3.2.1 运行时常量池
运行时常量池,其实算方法区的一部分。Class文件中除了有 类的版本、字段、方法、接口等信息外;还有一项信息就是常量池,用于存放编译期生成的字面量和字符引用,如下图:
四、直接内存
在JDK1.4中,新增加了一个 NIO(New Inout/Outinput)类,引入了一种基于通道(channel)与缓冲区(buffer)的I/O方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样在一些场景中能够显著提升技能,避免了数据再 Java 堆和 Native 堆中来回复制数据,常见的通道类型有:
FileChannel:从文件中读写数据
DatagramChannel:从UDP中读写数据
SocketChannel:从TCP中读写数据
ServerSocketChannel:用来监听 websocket 的连接
具体案例可以查找NIO的具体案例
直接内存,不是虚拟机运行时内存区的一部分,也不是Java规范中定义的内存区域。但既然是内存,如果 超过了 RAM 和 SWAP 寻址空间限制,还是会报OutOfMemoryError的。
评论