jvm理解
jvm内存主要分为:方法区、堆、虚拟机栈、本地方法区栈、程序计数器。
一、方法区(共享)
方法区内存放的是类信息、常量(jdk1.7及以上,把常量池移到了堆内存中)、静态变量等。当方法区无法满足内存分配需求时候会抛出OOM(OutOfMemoryError)
二、堆(共享)
虚拟机中最大的一块内存区域,存放所有实例对象。堆内存分为新生代和老年代两块。新生代由一块Eden和两块Survivor(一块为From Survivor,另一块为To Survivor,两块Survivor随时可以互换)组成,比例为比例为8:1:1。
年轻代的垃圾回收算法使用的是复制算法,其思想是把一块内存平均分为两个,当一块用完之后,把还活着的对象复制到另外一块中去,好处是不会才是内存碎片,缺点为内存利用率不高。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
三、虚拟机栈(线程私有)
本质上就是一个栈,里面存放的元素为栈帧。栈帧存放的为局部变量表(函数内部的变量)、操作数栈(执行引擎计算时需要)、方法出口等等。
每执行一个方法就会生成一个栈帧并压入栈内,待方法结束后会执行弹栈(出栈)。这个过程中可能会出现两种异常:一种为StackOverflowError,当前线程请求的栈深度大于虚拟机所允许的深度时,会抛出这个异常。另一种为OutOfMemoryError异常,当虚拟机栈可以动态扩展时(当前大部分虚拟机都可以),如果无法申请足够多的内存就会抛出OutOfMemoryError。
四、本地方法区栈(线程私有)
本地方法栈与虚拟机栈所发挥的作用很相似,他们的区别在于虚拟机栈为执行Java代码方法服务,而本地方法栈是为Native方法服务。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
五、程序计数器(线程私有)
每个线程当然得有个计数器记录当前执行到那个指令。如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
GC三种常见算法
1、标记-清除算法
通过可达性分析将可回收的对象进行标记,标记后再统一回收所有被标记的对象,标记过程其实就是可达性分析的过程。这种方法有2个不足点:效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量的不连续的内存碎片。
2、复制算法
见上面堆内存介绍
3、标记-整理算法
标记整理算法很简单,就是先标记需要回收的对象,然后把所有存活的对象移动到内存的一端。这样的好处是避免了内存碎片。
注:可达性,简单的说就是,根据GC-root(根节点)来往下挖掘,如果挖掘对象与树产生对象关联则把该对象挂在树上,最后再清理没有在树上的对象。