java 内存区域划分
方法区:
1 主要储存被虚拟机加载的类的信息,常量,静态变量,以及及时编辑器编译后的代码等数据
2 * 被线程共享
3 方法区里有一个 运行常量池 , 大部分用于存放静态编译产生的字面量和符号引用,运行时的常量也可能放在这个常量池里。
虚拟机栈:
1 为Java 方法服务,方法在执行的时候会创建一个栈帧,用于存储 局部变量表,操作数栈,动态链接与方法出口灯,
2 栈为线程私有,生命周期与线程相同。
3. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一
个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
4.操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
5.每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符
号引用在运行期转化为直接引用。
本地方法栈:跟虚拟机栈差不多,主要是为Native方法服务。
程序计数器:唯一一个不发生GC的地方 改变计数值执行字节码命令,分支,循环,跳转,异常处理
,线程恢复等,我感觉跟引用计数法有些像。
堆:对象的创建,一个回收常在这里发生。
判断GC对象
1 引用计数法
引用计数法就是给对象设置一个计数器 每当有一个地方引用这个对象 就将计数器加一,引用失效时,计数器就减一。
问题:循环引用,造成对象回收不掉。
2 可达性分析
从一个Gc Root 对象向下搜索,如果一个对象到GcRoot没有任何引用链相连,则说明此对象不可用。
GcRoot 对象
栈中的对象(基本)
方法区类静态属性引用的对象
方法区常量池引用的对象。
本地方法栈引用的对象
理解上面的 四个 需要对内存区域进行了解以及熟知。
满足上述条件后不一定会回收,还要经历两次标记的过程。
第一次标记 是否有必要执行 finealize()方法,当对象没有覆盖finealize()方法或者已经被虚拟机调用过,那么就认为没有必要,
如果有必要执行 finealize()方法,那么会将这个对象放在 F-Queue的队列中,虚拟机触发一个线程去执行,优先级底的线程,
此时Gc会对此对象进行第二次标记,在第二次标记之前,如果这个对象有新的引用,该对象被移除‘即将回收‘队列,如果没有,则等待回收。
Java 垃圾回收算法
1 标记-清除
标记回收的对象,然后统一回收。
优点:简单
缺点:
1 效率低,标记跟清除都效率低
2 导致大量的不连续的内存碎片,导致程序分配较大的对象,没有充足的内存提前进行一次GC(内存抖动)
2 复制算法
将内存分为两个相等的两部分,每次使用一块,当要使用完了,将还存活的对象复制到第二块内存板上,
然后清除第一块内存,然后在把存活的对象复制回来。(缺点:会浪费内存)
改进:
内存区域进行了重新划分, 8:1:1
较大的那块 叫 Eden区,剩下叫 Survior区
每次优先使用Eden区,弱Eden区满,讲对象复制到第二块内存,然后清除Eden区
如果存活的对象太多,以至于Survior不够,会将这些对象通过分配担保机制
复制到老年代中(Java 堆)。
3 标记-整理
区分 标记-清除,为了解决标记—清除 产生大量内存块碎片的问题。
当对象存活率较高的时候,也解决了复制算法的效率问题。
前两个算法的合集吧 在清除无用对象的时候讲对象移到另一端,然后清除掉边界以外的对象,这样就不会产
零碎的内存碎片了。
4 分代回收
根据对象的生命周期,将堆分为新生代跟老年代,(永久代),新生代对象生命周期存活的短(感觉这块是标记)
每次都会有大量的对象死去,采用复制算法,老年代对象存活时间长,所以可以考虑 标记-清除,或者标记—整理。
网友评论