JVM笔记【1】-- 运行时数据区

[TOC] # (一)java内存区域管理 C/C++每一个new操作都需要自己去delete/free,而java里面有虚拟机自动管理内存,不容易出现内存泄漏或者溢出的问题,但是不容易出现不代表不出现,了解虚拟机怎么使用和管理内存是十分重要的是,对程序优化或者问题排查有帮助。 运行时区域主要分为: - 线程私有: - 程序计数器:`Program Count Register`,线程私有,没有垃圾回收 - 虚拟机栈:`VM Stack`,线程私有,没有垃圾回收 - 本地方法栈:`Native Method Stack`,线程私有,没有垃圾回收 - 线程共享: - 方法区:`Method Area`,以`HotSpot`为例,`JDK1.8`后元空间取代方法区,有垃圾回收。 - 堆:`Heap`,垃圾回收最重要的地方。 ![image-20201222221827719](https://upload-images.jianshu.io/upload_images/13518088-b82b70a25d51f8ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 1.1 程序计数器 空间很小,当前线程执行的字节码的行号指示器(线程独有,指示当前执行到哪,下一步需要执行哪一个字节码),分支,循环,跳转,异常处理,线程恢复都需要依赖它。 线程私有:`java`多线程其实是线程轮流切换并分配处理器执行时间的方式实现,一个核一个具体的时间点,只会执行一个线程的指令。线程切换需要保存和恢复正确的执行位置(保护和恢复现场),所以不同的线程需要不同的程序计数器。 - 执行`java`方法时,程序计数器记录的是正在执行的字节码指令地址 - 执行`Native`方法,程序计数器为空 **唯一一个没有规定任何`OutOfMemory`的区域**,也没有GC(垃圾回收)。 ## 1.2 虚拟机栈 线程私有,生命周期和线程一样,主要是记录该线程Java方法执行的内存模型。虚拟机栈里面放着好多栈帧。**注意虚拟机栈,对应是Java方法,不包括本地方法。** 一个Java方法执行会创建一个栈帧,一个栈帧主要存储: - 局部变量表 - 操作数栈 - 动态链接 - 方法出口 每一个方法调用的时候,就相当于将一个栈帧放到虚拟机栈中(入栈),方法执行完成的时候,就是对应着将该栈帧从虚拟机栈中弹出(出栈)。 每一个线程有一个自己的虚拟机栈,这样就不会混起来,如果不是线程独立的话,会造成调用混乱。 大家平时说的java内存分为堆和栈,其实就是为了简便的不太严谨的说法,他们说的栈一般是指虚拟机栈,或者虚拟机栈里面的局部变量表。 局部变量表一般存放着以下数据: - 基本数据类型(`boolean`,`byte`,`char`,`short`,`int`,`float`,`long`,`double`) - 对象引用(reference类型,不一定是对象本身,可能是一个对象起始地址的引用指针,或者一个代表对象的句柄,或者与对象相关的位置) - returAddress(指向了一条字节码指令的地址) 局部变量表内存大小编译期间确定,运行期间不会变化。空间衡量我们叫Slot(局部变量空间)。64位的long和double会占用2个Slot,其他的数据类型占用1个Slot。 异常: - StackOverflowError:线程请求的栈深度大于虚拟机允许的深度 - OutOfMemoryError:内存不足 ## 1.3 本地方法栈 和虚拟机栈类似,对应本地方法,`Native`,虚拟机规范允许语言,使用方式和数据结构不同,有些可能将虚拟机栈和本地方法栈合并。 异常与虚拟机栈一致: - StackOverflowError:线程请求的栈深度大于虚拟机允许的深度 - OutOfMemoryError:内存不足 ## 1.4 java堆 堆是内存管理最大的一块,线程共享。 虚拟机规范中说,所有的对象实例和数组都要在堆上分配。但是实际上**不是所有的对象都在堆上分配**,这个和JIT编译器的发展和逃逸分析技术相关。Why? // TODO 堆的细分:新生代,老年代,再细分有Eden,From survivor,To survivor等。 堆中也有可能有线程私有的区域,分配缓冲区。 物理上可以不连续,但是逻辑上是连续的。 异常: - OutOfMemoryError:内存不足 ## 1.5 方法区 名为非堆,但是实际和堆一样,是线程共享的区域,主要存贮以下信息: - 已被虚拟机加载的类信息 - 常量 - 静态变量 - 即时编译器编译后的代码 方法区不等于永久代,指示Hotspot虚拟机将GC分代收集拓展到方法区,也就是用永久代实现了方法区,而其他的虚拟机不一定,不是固定的。JDK1.7将永久代的字符串常量移出了。 方法区回收垃圾的效果不是很好,可以选择不回收,虚拟机可以决定,当然也可能发生内存泄漏。 异常: - OutOfMemoryError:内存分配异常 ### 1.5.1 运行时常量池 运行时常量池时方法区的一部分,但是不是全部,`Class`文件主要包括: - 类的版本 - 字段 - 方法 - 接口 - 常量池,存放编译产生的字面量和符号引用,一般除了描述Class文件的符号引用,还有直接引用也在里面。是动态的,运行时可以产生,比如String.intern()方法。 异常: - OutOfMemoryError:内存分配异常 # (二)直接内存 不是虚拟机运行时数据区,也不是规范规定的区域,但是使用频繁且可能会有OutOfMemoryError:内存分配异常出现。 比如,NIO(1.4)基于Channel与Buffer的I/O,可以用Native函数直接分配堆外内存,通过存储在Java堆中的DirectByteBuffer对象作为引用来操作,提高性能,不需要Java堆和Native堆都来回复制数据。 直接内存受物理的内存,或者处理器寻址空间之类的限制。 本文系JVM学习相关笔记,整理来自周志明老师的《深入理解Java虚拟机》,无比钦佩,强烈推荐! **【作者简介】**: 秦怀,公众号【**秦怀杂货店**】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。

你可能感兴趣的:(JVM笔记【1】-- 运行时数据区)