一、java虚拟机内存区域划分
java虚拟机在执行java程序时会将管理的内存划分成几个区域
1、程序计数器
线程隔离的数据区,线程私有的内存,生命周期与线程相同
较小的内存区域,是当前线程执行的字节码的行号指示器,用于支持分支、循环、跳转、异常处理、线程恢复
如果执行的是Native的java方法,计数器为空,因为Native方法是java通过JNI直接调用本地c/c++库的接口,不会产生java相关的字节码,内存也不由jvm决定(在c内存模型上分配)
唯一一个在java虚拟机规范中没有规定OutOfMemoryError的区域,因为当线程执行到下一条指令的时候,只会改变当前程序计数器中保存的地址,不用申请新的内存来保存,不会内存溢出
2、java虚拟机栈
线程隔离的数据区,线程私有的内存,生命周期与线程相同
每个方法在执行的同时都会创建一个栈帧,用于储存局部变量表、操作数栈、动态链接、方法出口等信息
方法调用直至完成对应着栈帧的入栈出栈(如果方法中再调用方法,遵循先进后出)
如果线程请求的栈深度大于虚拟机允许的深度,会抛出StackOverflowError异常
如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,会抛出OutOfMemoryError异常
3、本地方法栈
和java虚拟机栈类似,本地方法栈为虚拟机使用到的Native方法服务
区别是当线程调用的是Native方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的Native方法
4、java堆
所有线程共享的内存区域,虚拟机启动时创建,生命周期和虚拟机进程一致
几乎所有的对象都在堆上分配内存,是垃圾回收的主要场所
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
5、方法区
所有线程共享的内存区域,虚拟机启动时创建,生命周期和虚拟机进程一致
储存已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码数据等
当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError异常
jdk1.7之前hotspot把方法区实现为永久代,和堆一样在虚拟机内存中
jdk1.7时hotspot把永久代中的字符串常量池移到了堆中
jdk1.8时hotspot把方法区实现为元数据,在本地内存中,字符串常量池还在堆中
二、垃圾收集
1、判断对象是否存活
(1)、引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1,引用失效时计数器减1,计数器为0就判断对象不被使用。
如果有两个不被使用的对象互相引用了,引用计数器算法就无法通知GC收集器回收他们
(2)、可达性分析算法
把虚拟机栈中引用的对象、静态属性引用的对象、常量引用的对象、JNI引用的对象作为GC Roots,当一个对象到GC Roots没有任何引用链时,判断这个对象是不可用的
java、C#等主流实现都是通过可达性分析算法
2、判断对象是否要被回收
经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots存在引用链时,被第一次标记,如果对象有必要执行finalize(),会被放在F-Queue队列中等待虚拟机自动建立的低优先级Finalizer线程去执行触发它的方法,如果对象在finalize中没有产生GC Roots的引用链,虚拟机会对这个对象进行第二次标记,确定回收对象
finalize()只能被执行一次,如果之前逃过一次回收的对象再次面临回收就没有机会了
3、垃圾收集算法
(1)、标记-清除算法
首先标记出所需要回收的对象,在标记完成后统一回收所有被标记的对象
缺点是标记和清除效率不高,清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致需要分配大对象时无法找到足够的连续内存,而提前触发另一次垃圾收集动作
(2)、复制算法
将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将还存活着的对象复制到另一块上面,然后再把已使用的内存空间一次性清理掉
实现简单运行高效,但是可用内存只有原来的一半
现在的商业虚拟机都采用这种收集算法来回收新生代,HotSpot将新生代分为Eden、Survivor1和Survivor2三块,比例是8:1:1,第一次回收将存活对象存进Survivor1,下一次回收将存活对象存进Survivor2,交替进行
(3)、标记-整理算法
在老年代中,如果还使用复制算法在对象存活率较高的时候就要进行较多的复制,会降低效率,而且没有额外空间可以分配担保,来应对所有对象都存活的情况
所以老年代一般使用标记-整理算法,类似标记-清除,不过在标记后不是直接对可回收对象进行清理,而是将存活的对象都向一端移动,然后直接清理掉另一端的内存
(4)、分代收集算法
一种垃圾收集的方法,把java堆分为新生代和老年代,新生代因为每次垃圾收集只有少量对象存活,使用复制算法,老年代因为存活率高、没有额外空间进行分配蛋堡,使用标记-清理或标记-整理算法,当前的商业虚拟机的垃圾收集都采用这种方法
4、垃圾收集器
新生代垃圾收集器:Serial、ParNew、Parallel Scavenge
老年代垃圾收集器:Serial Old、Parallel Old、CMS收集器
全代垃圾收集器:G1收集器
(1)、Serial
5、内存的分配与回收
三、java程序实际内存占用
四、jvm参数调优