最近面试时经常会被问到JVM以及内存分配的问题,觉得有必要学习总结一下下~~~
一、Java内存区域
Java中,虚拟机自动进行内存管理,在Java虚拟机执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,主要包含以下几个部分:
1.程序计数器:
当前线程所执行的字节码的行号指示器;
字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
线程私有:为了线程切换后能够恢复到正确的执行位置,每个线程需要有一个独立的程序计数器;
JAVA方法,计数器记录正在执行的
虚拟机字节码指令的地址
native方法,该计数器值为空
唯一没有OutOfMemoryError情况的区域
2.Java虚拟机栈:
线程私有,生命周期与线程相同;
虚拟机栈描述Java方法执行的内存模型,
每个方法在执行时会创建栈帧(其实就是分配虚拟机栈的空间);
异常:当线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverFlowError异常;
Java虚拟机扩展时无法申请到足够的内存,抛出OutOfMemoryError异常
3.本地方法栈:
为本地方法服务
使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用
4.Java堆:
线程共享,在虚拟机启动时创建,此内存区域的唯一目的是存放对象实例,几乎所有对象实例都在这里分配内存;
所有对象实例和数组都要在堆上分配;
Java堆是GC的主要区域,分为新生代、老生代;
Java堆可以在物理上不连续的内存空间中,逻辑连续;
5.方法区
线程共享的内存区域,它用于存储
已经被加载的类信息、常量、静态变量、即时编译器编译后的代码等
可以选择不实现垃圾收集,垃圾回收主要针对对常量池的回收和对类型的卸载
当方法区不能满足内存分配需求时,会抛出OutOfMemoryError异常;
运行时常量池:
存放编译器生成的各种字面量(基本类型)和符号引用
String.intern()在运行时可以将新的常量放入池中;
二、Java内存分配
其实上面已经谈到了Java对象的内存分配机制,下面再详细说一下吧:
Java中把一切都视为对象,但是操纵对象其实是通过“引用”;在Java中,通过new创建的对象都在堆区进行内存分配,栈中存储的是对象引用,基本数据类型算是一个特例
1.基本数据类型
基本数据类型在Java中有些“特殊”,对于类似于:
int a = 1;
定义的变量称为自动变量,自动变量存在的是字面值,即不是类的实例,也不是类的引用;
直接将变量存储在栈中,并且存储的是“值”,并不是对象引用;
关于包装类型:
Java中为8种基本类型也提供了相应的包装类,对于包装类,既可以使用new来创建对象,也可以使用基本类型的方法直接赋值;
Integer特殊点:Integer类将[-128,127]之间的数字进行了缓存,也就是说使用Integer a = x;的方法创建整型对象时,该范围内的对象直接使用和基本类型一样的方式创建,使用 ==比较两个值相等的对象时,直接比较字面量,所以会返回true;对于该范围之外的对象,需要使用和普通对象相同的方式进行创建,使用==比较时会比较内存地址,所以两个值相同的Integer对象也会返回false;
Java的8种基本类型都实现了常量池技术;
2.引用类型
引用类型通过new来创建一个新的对象实例,这个对象在堆区进行内存分配,栈中存储着指向该对象内存地址的一个引用;