- Jvm的组成、垃圾回收机制
1.1 Jvm组成
JVM有6个部分:JVM解释器、指令系统、寄存器、栈、存储区和碎片回收。
1)JVM解释器
虚拟机处理字段码的CPU。
2)指令系统
指令由操作码和操作数组成。
3)寄存器
JVM有自己的虚拟寄存器,可以快速的与JVM的解释器进行数据交换。
4)栈
指令执行时数据和信息存储的场所和控制中心,提供给JVM解释器运算所吸引的信息。
5)存储区
存储区分为两类:常量缓冲池(存取名称、方法和字段名、串常理)和方法区(Java方法的字节码)。
6)碎片回收
用过的java类的具体实例从内存进行回收,开发人员免去控制内存的麻烦。分代收集技术,利用对象在程序中生存的时间划分成代,以此为标准进行碎片回收。
1.2 垃圾回收机制
GC看对象是否活动确定是否收集对象。
1)触发GC的条件
GC在线程运行优先级最低,没有线程在运行时被调用。堆内存不足时GC会被调用,内存不足强制调用GC线程。如果还是不足会两次调用GC,如果还不足,将会报错”out of memory”,java应用将停止.
- Scavenge GC 新对象生成,在Eden申请空间失败,Eden区GC频繁。
- Full GC 对整个堆进行整理,包括Young、Tenured和Perm。较慢,使用次数应尽量减少。触发条件:1)年老代被写满;2)持久代被写满;3)System.gc()被显示调用;4)上一次GC之后Heap的各域分配策略动态变化。
2)方法
A. System.gc()方法 不管有哪种垃圾回收算法,都可以请求java垃圾回收。java -verbosegc classfile查看堆内存。
B. Finalize()方法 缺省机制来终止该对象心释放资源。protected void finalize() throws Throwable在finalize()方法返回之后,对象消失,垃圾收集开始执行。
使用Finalize()是存在着垃圾回收器不能处理的特殊情况,防止内存泄漏。
1.3 减少GC开销的措施
(1)不显示调用System.gc()。会增加间歇性停顿的次数,大大影响系统内存。
(2)减少临时对象的使用。临时对象在跳出函数调用会成为垃圾,减少使用,减少主GC的机会。
(3)对象不用时最好显示置为null。一般为null就要被处理,提高处理效率。
(4)尽量使用StringBuffer,而不用String来累加,每次改变就会创造新的对象。
(5)能使用基本类型Int,Long,就不用Integer、Long对象。最好使用基本变量。
(6)尽量减少使用静态对象变量。静态变量属于全局变量,不会被GC回收,会一直占用内存。
(7)分散对象创建或删除的时间。集中处理会导致大量需要内存,面对这种情况主GC频率会增加,会导致系统性能。
1.4 对象在JVM堆区的状态
(1)可触及状态。程序中还有变量引用,此对象为可触及状态。
(2)可复活状态。finalize方法内的代码有可能将对象转为可触及状态,否则对象转化为不可触及状态。
(3)不可触及状态,就被GC回收。
1.5 常用垃圾收集器
(1)标记-清楚收集器mark-Sweep
(2)复制收集器 Copying
(3)标记-压缩收集器 Mark-Compact
(4)分代收集器 Generational
1.6 垃圾回收算法
(1)tracing算法-标记清除
标记清除回收器。标记阶段用于标记需要回收的对象,清除阶段是把标记对象清楚。采用从根集合(GC Roots)进行扫描。
(2)Copying算法-复制
把内存容量分为大小相等的块,当一块内存用完了,还有存活着的对象复制到另一块当中,不容易出现内存碎片。从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面。
(3)Compacting算法-标记整理
首先进行标记,标记完之后不是直接清理回收对象,而是移动到一端,然后清理到边界外的内存。成本高,但是解决了内存碎片的问题。
(4)Generation算法-分代收集
分代收集算法,常用算法。核心思想根据对象存活的生命周期将内存划分为若干不同的区域。分为老年代和新生代,老年代特点每次垃圾收集只有少量对象需要被回收,新生代特点是每次回收都有大量对象需要被回收,可以根据不同代的特点采取最适合的收集算法。
新生代:新创建的对象存放的位置。当对象从这块内存区域消失时,发生了一次“minor GC”。
老年代:被复制过来的年轻对象,GC发生的次数比年轻代少。major GC”(或“full GC”)发生。少量对象需要被回收。
1.7 JVM把内存划分成了如下几个区域
1.方法区(Method Area)称为持久代,加载类信息、静态变量、构造函数、final定义的常量等,全局共享,执行GC情况少。超出大小会抛出OutOfMemory:permGen Space异常。运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译器生成的常量和引用。
2.堆区(Heap)GC最频繁,所有线程共享,在虚拟机启动时候创建。注意存放对象实例及数组,new出来的对象都存储在该区域。
3.虚拟机栈(VM Stack)占用操作系统内存,一个线程对应一个虚拟机栈,线程私有,每个方法被执行时产生一个栈帧stack frame,存储局部变量,调用方法,栈帧入站,调用结束,出栈。局部变量表,存取方法相关局部变量,编译期间确定,运行时不再改变。
虚拟机栈异常有StackOverFlowError(栈溢出,线程调用栈深度大于虚拟机允许的深度)和OutOfMemoryError(内存溢出,一般不允许动态扩充,线程可以一直申请栈,直到内存不足,抛出该异常)。
4.本地方法栈(Native Method Stack)native方法的执行,存储了每个native方法的执行状态。
5.程序计数器(Program Counter Register)很小的内存区,在cpu上面,程序员无法操作它。作用时:JVM解释字节码.class文件时,存储当前线程执行的字节码的行号。改变程序计数器来取下一条指令、分支、循环等。其属于线程私有,与计数器一一对应。Java方法记录虚拟机字节码指令地址,native则计数器值为空。此内存不会抛出OutOfMemory.
Android要不要手动调用System.gc()呢?
陆奇:如果不懂产品,不可能成为一个好的工程师,不仅要动产品,还需要动商业,动生态,因为你的工作的责任是把需求,把平台、把开发流程、把你的团队为将来做准备。
上一节说了JVM的垃圾回收机制,那么Android要不要手动调用System.gc()呢?
在Android5.0之前,可以直接调用gc
public static void gc() {
Runtime.getRuntime().gc();
}
在Android5.0之后,需要:
1)System.gc()和System.runFinalization()同时调用
2)直接调用Runtime.getRuntime().gc()
/**
* @android 5 之后调用 gc
*/
private static boolean justRunFinalization;
//private static int lock = 0;
public static void gc(){
boolean ifRunGC;
synchronized(lock){
ifRunGC = justRunFinalization;
if(ifRunGC){
justRunFinalization = false;
}else{
runGC = true;
}
}
if(ifRunGC){
Runtime.getRuntime().gc(); //只有当shouldRunGC为true时,才会真的去gc.shouldRunGC即justRanFinalization
}
}
public static void runFinalization(){ //执行对象的finalize()方法
boolean ifRunGC;
synchronized(lock){ //只有runGC为true才会触发gc
ifRunGC = runGC;
runGC = false;
}
if(ifRunGC){
Runtime.getRuntime().gc();
}
Runtime.getRuntime().runFinalization();
synchronized(lock){
justRunDinalization = true; //justRunDinalization才会赋值为true
}
}
扫码关注一起随时随地学习!!!就在洋葱攻城狮,更多精彩,等你来!!