Java的GC机制

来源:互联网 发布:万网域名登陆 编辑:程序博客网 时间:2024/06/07 23:48

jVM内存模型

java和C/C++最大的一个区别就是自动的内存回收机制。Java的内存模型经典图例如下:
Java内存机制
这里重点注意三个地方:堆内存、方法区、与栈内存。其中堆内存与方法区又合称为堆内存。在程序未实例化之前,类的模型以及全局变量都是保存在方法区中。类的加载方法分为两种:

显式加载:Class.forName()利用Class的静态方法对类进行实例化。隐式加载:通过new关键字加载。

当类实例化之后,实例就按照方法区类的模板加载到了堆内存。此时栈内存存放基本的数据类型(int,float,short,long,char等)以及指向堆对象的引用变量。总之,堆是用来存放对象的,栈是用来执行程序的,相较于堆,栈的执行速度较快。
这里需要重点注意的是Java中的常量池。由于版本原因,运行时常量池在JDK1.6及之前版本的JVM中是方法区的一部分,而在HotSpot虚拟机中方法区放在了”永久代(Permanent Generation)”。所以运行时常量池也是在永久代的。 但是JDK1.7及之后版本的JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。
这里拿int变量与string变量来作为例子。int与string变量最大的区别就是int存在它自身的基本数据类型,在int,integer,new integer()之间存在相对复杂的变化。
int:
基本数据类型与对象间比较,在JDK1.5以后存在自动拆箱机制,不论后面其它哪个相比较,都会被向下转型到int,然后比较其数值。
Integer与Integer间的比较,从jdk1.5开始,有“自动装箱”这么一个机制,在byte-128到127范围内,如果存在了一个值,再创建相同值的时候就不会重新创建,而是引用原来那个,但是超过byte范围还是会新建的对象。
关于new integer() 这里涉及到和int比较不再赘述,都自动拆箱比较数值。但和integer进行比较的时候,当integer在integer cache中时候,显然不等,当超出integer cache范围时候,此时integer相当于new integer()两个new比较都在堆中创建了对象,对应不同的地址,显然不相等。

public class test {    public static void main(String[] args) {        int i0=1000;        Integer i1=1000;        Integer i2=1000;        Integer i3=2000;        Integer i11=new Integer(1000);        Integer i22=new Integer(1000);        Integer i33=new Integer(2000);        System.out.println(i0==i11);//true        System.out.println(i0==i1);//true        System.out.println(i1==i2);//false不会有自动拆箱        System.out.println(i1==i11);//false        System.out.println(i11==i22);//false        System.out.println(i3==(i1+i2));//true后面的这些加加减减全部都拆箱为基本数据类型。        System.out.println(i3==(i11+i22));//true        System.out.println(i33==(i1+i2));//true        System.out.println(i33==(i11+i22));//true    }}

string:

问:String s = new String(“xyz”); 产生几个对象?答:一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个。

字符串加法:JVM对于字符串常量的”+”号连接,将程序编译期,JVM就将常量字符串的”+”连接优化为连接后的值。由final修饰和无final修饰:有final修饰的字符串能在编译前确定,无final修饰的字符串是无法在编译前确定的。

//字符串相加JVM优化String a = "a1";  String b = "a" + 1;  System.out.println((a == b)); //result = true  String a = "atrue";  String b = "a" + "true";  System.out.println((a == b)); //result = true  String a = "a3.4";  String b = "a" + 3.4;  System.out.println((a == b)); //result = true  //字符串相加无final关键字String a = "ab";  String bb = "b";  String b = "a" + bb;  System.out.println((a == b)); //result = false   //字符串相加有final关键字String a = "ab";  final String bb = "b";  String b = "a" + bb;  System.out.println((a == b)); //result = true   

Java中的内存溢出

首先我们来区别两个概念:

内存溢出:程序在申请内存空间时,没有足够的内存空间供其使用。内存泄漏:程序在申请内存后,无法释放已经申请的内存空间。

沿袭上述所述,Java中的堆的溢出,主要原因就是对象实例化过多。栈的溢出主要是因为线程需要栈深度大于虚拟机本身。这里尤其注意在多线程的环境之中,可以通过减小最大堆和减小栈容量来创建更多的线程。
关于内存泄漏,常见原因是对象在使用后没有断开引用。例如(1)各类连接如SQL、IO。(2)变量不合理的作用域。(3)面试常考:单例设计模式也会导致内存泄漏。

//核心:单例模式中存在对于指向其它实例的指针class Bigclass{    //class body}class Singleton{    private Bigclass bc;    private static Singleton instance=new Singleton(new Bigclass());    private Singleton(Bigclass bc){this.bc=bc}    public Singleton getinstance{        return instance;    }}

关于GC机制

判断方法

引用计数法:略。无法解决互相引用的问题。可达性分析法:通过一系列的 gc root(栈指针,方法区中static相关已经常量引用) 进行树搜索。

回收算法

标记-清除法:效率问题以及内存碎片。复制算法:仅仅使用1/2内存。内存开销较大。但实际上98%的对象是朝生夕死的,所以不需要按照1:1的比例划分,而是以8:1的比例(hotspot默认)划分为Eden和survivor。分代收集法:jvm中的GC算法。新生代采用复制算法,因为有大量对象死去。老年代采用标记整理或者标记清除。

回收内存模型:
回收内存模型

由分代收集再谈几类引用:

强引用:怎么都不会被GC机制回收。宁愿报错都不会回收该区域。软引用:只有在内存不足时,才会被回收。JDK1.2以后提供softreference类。弱引用:weak为关键字(weakhashmap),一旦运行system.gc()则立马被回收虚引用:就和没有引用一样。

system.gc()与finalize

system.gc()调用的是runtime中的gc方法。对象回收前会调用finalize方法,但也只会调用一次,此时可以通过finalize 完成引用对自我进行最后一次拯救(参见《深入理解JVM》)。不同于C++中的析构函数,C++调用析构函数对象一定会被销毁。
此外调用GC时会STOP THE WORLD现象,因为不能存在在垃圾回收时,引用关系还在不断变化的情况。这里又引入了安全点的概念。所谓安全点,是以是否具有让程序具有长时间执行的特征来选定的。如一些循环操作。在随后运行GC时又存在抢先式中断主动式中断。注意其中概念与区别。目前的hotspot主要采取主动式中断轮询各个线程是否到达安全点。

fullgc与minor gc:

Minor GC:当Eden区满时,触发Minor GC。
Full GC:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小