首先我们要知道jvm虚拟机内存空间大致分为“堆(Heap)”,“栈(Stack)”,”方法区(Method Area)“。因为jdk1.8之后有些不同,这里我们还是以jdk1.8之前的来讲述,jdk1.8的放在最后。
栈(线程私有)
栈分为程序计数器,虚拟机栈,本地方法栈。
程序计数器:
程序计数器可以看作程序的管理容器。线程都有一个独立的程序计数器,为了能够知道线程切换之后能恢复到正常的位置。线程是独立的,程序计数器也是独立的。两个计数器之前相互独立,独立存储,我们称这为”线程私有“内存。 上面介绍中的程序计数器主要有两个作用。
- 字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制。(例如“顺序执行”,“选择”,“循环”,“异常处理”)
- 在多线程的情况下,程序计数器用来记录当前线程的执行位置,从而当线程被切换回来的时候能够知道该线程执行到了什么位置。
** (程序计数器是唯一一个不会出现OutMemoryEorror的区域,随着线程的创建而创建,随着线程的消亡而消亡) **
虚拟机栈(VM stack):
首先我们需要知道,栈是属于线程私有的,所以它的生命周期是和线程生命周期一样,随着线程的创建而创建,随着线程的消亡而消亡。虚拟机栈也不例外,所以虚拟机栈通常用来存放局部变量,包括编译器可知的各种数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型,不是对象本身,可能是指向对象的引用指针,也可能是指向一个代表对象的句柄或者于此对象相关的位置)。 在虚拟机栈通常会抛出两种异常(StackOverFlowError和OutMemoryError) *StackOverFlowError:若Java虚拟机的内存大小不允许动态扩展,那么当线程请求栈时的深度超过当前Java虚拟机栈的最大深度的时候,就会抛出StackOverFlowError异常。 *OutMemoryError:若虚拟机栈的内存大小允许动态扩展并且当虚拟机请求栈时内存用完的时候,就会抛出这个异常。
** Java栈可以类比数据结构中的栈,Java栈中保存的主要内容是线帧。函数调用就是压栈的过程,每一个函数的调用都会有一个对应的线帧被压到Java栈,每一个函数的调用结束后,就会有一个线帧被弹出。判断调用结束的方法就是return语句/抛出异常 **
本地方法栈(Natvie Method Stack)
和虚拟机栈发挥的作用相似,区别是虚拟机栈为虚拟机执行Java方法服务,而本地栈为虚拟机为使用的本地Native方法服务。 ⽅法执⾏完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和OutOfMemoryError 两种异常。
堆(线程共享)
这是jvm虚拟机中内存最大的一块区域,Java所有线程共享的区域。** 在虚拟机启动的时候创建 ** 。这块区域的唯一目的就是存放对象实例,几乎所有的对象实例都存放在这里以及数组都在这里分配内存。 我们常说的垃圾回收机制(GC)说的就是在这块区域进行的。由于现在收集器都在采用分代垃圾收集算法,所有Java堆还可以细分为新生代,老年代。再细致一点的话,Eden,From Survivor,To Survivor空间等。进一步划分的目的是为了更好的回收内存或者更快的分配内存。
方法区(线程共享)
方法区和Java堆一样是一个线程共享的空间,它用来存储已经被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码这些数据。于是方法也被称为“永久代”。而且为了和Java堆进行区分,他还有一个别名非堆(Non-Heap)。
JDK1.8之前永久代还没被彻底移除的时候通常通过下面参数调节方法的大小
常用参数:
-XX:PermSize=N //⽅法区(永久代)初始⼤⼩
-XX:MaxPermSize=N //⽅法区(永久代)最⼤⼤⼩,超过这个值将会抛出
OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
相对而言,垃圾收集行为在这个空间是比较少出现的,因为在这个空间可以理解为“永久存在”了,
jdk1.8,移除了方法区(HotSpot的永久代),取而代之的是元空间,元空间使用的是直接内存。
下面是一些常用参数:
-XX:MetaspaceSize=N //设置Metaspace的初始(和最⼩⼤⼩)
-XX:MaxMetaspaceSize=N //设置Metaspace的最⼤⼤⼩
和永久代不同的是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可能使用的系统内存。
总结
总的来说,JVM内存到这里就差不多,后面的还有一些关于对象创建过程晚点再填坑吧!(需要补坑的实在太多了~)