目录
内存管理是什么
JVM内存区域组成
程序计数器PC
java虚拟机栈
本地方法栈
JAVA堆
方法区
常量池
运行时常量池
内存管理是什么
Java的内存管理就是对象的分配和释放问题
分配 :内存的分配由程序完成的,程序员通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆中分配空间
释放 :对象的释放由垃圾回收机制决定和执行
GC需要监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等
JVM内存区域组成
从逻辑上可将JVM内存分为5个部分,主要分为被所有线程共享的内存区域和仅被当前线程独占的内存区域
线程共享的内存区域包括堆和方法区
线程独占的内存区域包括虚拟机栈,本地方法栈,程序计数器
写程序时,需要判断当前数据读写的是存在于哪类内存区域,如果存在的是线程共享的内存区域,那么就要考虑是否存在线程安全问题,如果存在线程独占的内存区域,就没关系
程序计数器PC
线程私有的内存区域
每个线程都有自己的一个程序计数器,来表示当前线程执行的字节码行号
字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令
如果线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果线程执行的是一个Native方法,计数器的值为空
java虚拟机栈
线程私有的内存区域
栈以帧为单位保存线程的状态,JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作
每个方法执行的同时会创建一个栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
栈帧中存放的局部变量有8种基本数据类型,以及引用类型(对象的内存地址)
当线程请求的栈的深度超出了虚拟机栈允许的深度时,会抛出StackOverFlow的错误
当Java虚拟机动态扩展到无法申请足够内存时会抛出OutOfMemory的错误
public void funcA() {
int n = 3;
funcB(n);
}
public void funcB(int n) {
System.out.println("");
}
以上代码运行时,程序会先调用funcA方法,将funcA方法封装成"栈帧”入栈,由于funcA方法中调用了funcB,将funcB方法封装成"栈帧”入栈
先执行B中的逻辑,等于funcB栈帧出栈操作
然后执行A方法,等于funcA栈帧出栈操作
本地方法栈
线程私有的内存区域
本地方法栈与虚拟机栈的区别:
虚拟机栈为虚拟机执行Java方法服务(也就是字节码),而本地方法栈为虚拟机使用到的Native方法服务
(Native方法:非java语言实现的函数,往往是由C/C++编写的,和操作系统相关性比较强的底层函数)
Java虚拟机规范对这个区域规定了两种异常情况:StackOverflowError 和 OutOfMemoryError异常
JAVA堆
每个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。
应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享
Java堆属于线程共享区域,所有的线程共享这一块内存区域
Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
Java堆是垃圾回收器管理的主要区域,从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以Java堆可以细分为:新生代、老生代
当Java虚拟机动态扩展到无法申请足够内存时会抛出OutOfMemory的错误
Java堆内存的OOM异常:
内存泄露:指程序中一些对象不会被GC所回收,它始终占用内存
内存溢出:程序运行过程中无法申请到足够的内存而导致的一种错误
方法区
线程共享区域
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
Java编译器是javac.exe把Java源文件编译成Java类文件,类加载的第一个阶段叫做“加载”,在这个阶段内,虚拟机将会读取被编译的Class文件生成Class对象,Class对象存储了一些类型信息,这些信息就是存储在方法区内
当方法区无法满足内存分配的需求时,将抛出OutOfMemoryError异常
常量池
常量池用于存放编译期生成的各种字节码和符号引用
类和类之间是有调用关系的,怎么记录这些调用关系呢,可以通过类似指针的方式,在类A的字节码中留下一个指针,指向想要调用的类B的字节码,这里指针就起到了“链接”的作用
在类加载中,在虚拟机加载字节码的时候,首先加载的就是一些静态的“符号引用”,然后在类加载的“链接”阶段或者说程序运行时将符号引用转化为直接引用
这些符号引用信息就存储在常量池中
运行时常量池
运行时常量池属于方法区的一部分
运行时常量池存储着两大类数据:
1、编译期间产生的字节码中定义的静态信息,比如:
--字节码生成的Class对象(比如常量池)
--由字节码生成的字面量(编写代码中所定义的常量自变量)
2、运行期间产生的信息
--运行时会将一部分符号引用转换为直接引用,将直接引用保存在运行时常量池
--常见的字符串常量池