java中垃圾回收机制的原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中垃圾回收机制的原理相关的知识,希望对你有一定的参考价值。

java中垃圾回收机制的原理
推荐一篇文章:
对高性能JAVA代码之内存管理
更甚者你写的代码,GC根本就回收不了,直接系统挂掉。GC是一段程序,不是智能,他只回收他认为的垃圾,而不是回收你认为的垃圾。
GC垃圾回收:
Grabage Collection相信学过JAVA的人都知道这个是什么意思。但是他是如何工作的呢?
首先,JVM在管理内存的时候对于变量的管理总是分新对象和老对象。新对象也就是开发者new出来的对象,但是由于生命周期短,那么他占用的内存并不是马上释放,而是被标记为老对象,这个时候该对象还是要存在一段时间。然后由JVM决定他是否是垃圾对象,并进行回收。
所以我们可以知道,垃圾内存并不是用完了马上就被释放,所以就会产生内存释放不及时的现象,从而降低了内存的使用。而当程序浩大的时候。这种现象更为明显,并且GC的工作也是需要消耗资源的。所以,也就会产生内存浪费。
JVM中的对象生命周期里谈内存回收:
对象的生命周期一般分为7个阶段:创建阶段,应用阶段,不可视阶段,不可到达阶段,可收集阶段,终结阶段,释放阶段。
创建阶段:首先大家看一下,如下两段代码:
test1:
for( int i=0; i《10000; i++)
Object obj=new Object();
test2:
Object obj=null;
for( int i=0; i《10000; i++)
obj=new Object();
这两段代码都是相同的功能,但是显然test2的性能要比test1性能要好,内存使用率要高,这是为什么呢?原因很简单,test1每次执行for循环都要创建一个Object的临时对象,但是这些临时对象由于JVM的GC不能马上销毁,所以他们还要存在很长时间,而test2则只是在内存中保存一份对象的引用,而不必创建大量新临时变量,从而降低了内存的使用。
另外不要对同一个对象初始化多次。例如:
public class A
private Hashtable table = new Hashtable();
public A() table = new Hashtable();
// 这里应该去掉,因为table已经被初始化。

这样就new了两个Hashtable,但是却只使用了一个。另外一个则没有被引用。而被忽略掉。浪费了内存。并且由于进行了两次new操作。也影响了代码的执行速度。
应用阶段:即该对象至少有一个引用在维护他。
不可视阶段:即超出该变量的作用域。这里有一个很好的做法,因为JVM在GC的时候并不是马上进行回收,而是要判断对象是否被其他引用在维护。所以,这个时候如果我们在使用完一个对象以后对其obj=null或者obj.doSomething()操作,将其标记为空,可以帮助JVM及时发现这个垃圾对象。
不可到达阶段:就是在JVM中找不到对该对象的直接或者间接的引用。
可收集阶段,终结阶段,释放阶段:此为回收器发现该对象不可到达,finalize方法已经被执行,或者对象空间已被重用的时候。
JAVA的析构方法:
可能不会有人相信,JAVA有析构函数? 是的,有。因为JAVA所有类都继承至Object类,而finalize就是Object类的一个方法,这个方法在JAVA中就是类似于C++析构函数。一般来说可以通过重载finalize方法的形式才释放类中对象。如:
public class A
public Object a;
public A() a = new Object ;
protected void finalize() throws java.lang.Throwable
a = null; // 标记为空,释放对象
super.finalize(); // 递归调用超类中的finalize方法。


当然,什么时候该方法被调用是由JVM来决定的。..。..。..。..。..。..。..。.
一般来说,我们需要创建一个destory的方法来显式的调用该方法。然后在finalize也对该方法进行调用,实现双保险的做法。
由于对象的创建是递归式的,也就是先调用超级类的构造,然后依次向下递归调用构造函数,所以应该避免在类的构造函数中初始化变量,这样可以避免不必要的创建对象造成不必要的内存消耗。当然这里也就看出来接口的优势。
数组的创建:
由于数组需要给定一个长度,所以在不确定数据数量的时候经常会创建过大,或过小的数组的现象。造成不必要的内存浪费,所以可以通过软引用的方式来告诉JVM及时回收该内存。(软引用,具体查资料)。
例如:
Object obj = new char[10000000000000000];
SoftReference ref = new SoftReference(obj);
共享静态存储空间:
我们都知道静态变量在程序运行期间其内存是共享的,因此有时候为了节约内存工件,将一些变量声明为静态变量确实可以起到节约内存空间的作用。但是由于静态变量生命周期很长,不易被系统回收,所以使用静态变量要合理,不能盲目的使用。以免适得其反。
因此建议在下面情况下使用:
1,变量所包含的对象体积较大,占用内存过多。
2,变量所包含对象生命周期较长。
3,变量所包含数据稳定。
4,该类的对象实例有对该变量所包含的对象的共享需求。(也就是说是否需要作为全局变量)。
对象重用与GC:
有的时候,如数据库操作对象,一般情况下我们都需要在各个不同模块间使用,所以这样的对象需要进行重用以提高性能。也有效的避免了反复创建对象引起的性能下降。
一般来说对象池是一个不错的注意。如下:
public abstarct class ObjectPool
private Hashtable locked,unlocked;
private long expirationTime;
abstract Object create();
abstract void expire( Object o);
abstract void validate( Object o);
synchronized Object getObject()。..;
synchronized void freeObject(Object o)。..;
这样我们就完成了一个对象池,我们可以将通过对应的方法来存取删除所需对象。来维护这快内存提高内存重用。
当然也可以通过调用System.gc()强制系统进行垃圾回收操作。当然这样的代价是需要消耗一些cpu资源。
不要提前创建对象:
尽量在需要的时候创建对象,重复的分配,构造对象可能会因为垃圾回收做额外的工作降低性能。
JVM内存参数调优:
强制内存回收对于系统自动的内存回收机制会产生负面影响,会加大系统自动回收的处理时间,所以应该尽量避免显式使用System.gc(),
JVM的设置可以提高系统的性能。例如:
java -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=8 -Xms512m -Xmx512m
具体可以查看java帮助文档。我们主要介绍程序设计方面的性能提高。
JAVA程序设计中有关内存管理的其他经验:
根据JVM内存管理的工作原理,可以通过一些技巧和方式让JVM做GC处理时更加有效。,从而提高内存使用和缩短GC的执行时间。
1,尽早释放无用对象的引用。即在不使用对象的引用后设置为空,可以加速GC的工作。(当然如果是返回值。..。.)
2,尽量少用finalize函数,此函数是JAVA给程序员提供的一个释放对象或资源的机会,但是却会加大GC工作量。
3,如果需要使用到图片,可以使用soft应用类型,它可以尽可能将图片读入内存而不引起OutOfMemory.
4,注意集合数据类型的数据结构,往往数据结构越复杂,GC工作量更大,处理更复杂。
5,尽量避免在默认构造器(构造函数)中创建,初始化大量的对象。
6,尽量避免强制系统做垃圾回收。会增加系统做垃圾回收的最终时间降低性能。
7,尽量避免显式申请数组,如果不得不申请数组的话,要尽量准确估算数组大小。
8,如果在做远程方法调用。要尽量减少传递的对象大小。或者使用瞬间值避免不必要数据的传递。
9,尽量在合适的情况下使用对象池来提高系统性能减少内存开销,当然,对象池不能过于庞大,会适得其反.
参考技术A

    GC的工作原理: 引用计数,标记复制"引用计数"是一种简单但速度很慢的垃圾回收技术.所有对象都有一个引用计数器,当有引用连接时计数器加1,当引用离开作用域时或者被置于NULL时,计数器-1,垃圾回收器会在所有包含对象引用的列表上进行遍历,当发现某个对象的引用计数为0时,就释放占用的空间."标记复制"的运行机制,垃圾回收器遍历包含所有引用的列表,当发现存活的对象引用时做上标记,这样当遍历完所有对象引用并做上标记的时候,执行垃圾回收,将没有标记的对象堆空间释放.

    垃圾回收机制的优点:Java的垃圾回收机制使得java程序员不用担心内存空间的分配,减少了内存溢出.但同时也牺牲了一定的性能.

参考技术B 在你写的程序中 编好的代码全部运行在 JVM 即 java 虚拟机,这时你的代码就会占内存。而内存中运行着jvm , jvm 的 heap 和 stack 都是有限大的。你的程序代码 过多占用内存。导致硬件出问题 系统运行不流畅 慢 ,这时 就产生了GC GC自动回收那些 已死的代码对象 或 已经无用的类

判断无用的类 标准
1.该类所有的实例都以回收。也就是Java堆中不存在该类的任何实例
2. 加载该类的classLoader已经被回收
3.该类对应的java.lang.class 对象没有在任何地方被引用, 无法在任何地方通过反射访问该类的方法

无用的类 和对象就会自动被 GC垃圾回收机制回收。。。。
参考技术C 对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。
通常,GC 采用有向图的方式记录和管理堆(heap) 中的所有对象。通过这种方式确定哪些对
象是"可达的",哪些对象是"不可达的"。当GC 确定一些对象为"不可达"时,GC 就有责任回
收这些内存空间。程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言
规范并不保证 GC 一定会执行。
参考技术D 回收机制就是 当一些资源被创建使用之后或不在调用的话 就会变成垃圾,垃圾的话会占用空间,这时候就需要释放空间给其他程序使用,所以JAVA拥有自动垃圾回收机制,会在适当的时候自动调用垃圾回收 释放资源,程序员也可以手动调用释放资源等等。
如:
public class A
String str;
public A(String str)
this.str=str;

public String toString()
return str;

@Override
protected void finalize() throws Throwable
System.out.println("我是"+str+".我被回收了..");

public static void main(String[] args)
A a=new A("李四");
a=null;
System.gc(); //运行结果 我是李四.我被回收了..

JVM 及 垃圾回收机制原理

JVM

Java 虚拟机 Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制。JVM实现了Java语言最重要的特征:即平台无关性。原理:编译后的 Java 程序指令并不直接在硬件系统的 CPU 上执行,而是由 JVM 执行。JVM屏蔽了与具体平台相关的信息,使Java语言编译程序只需要生成在JVM上运行的目标字节码(.class),就可以在多种平台上不加修改地运行。Java 虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。因此实现java平台无关性。它是 Java 程序能在多平台间进行无缝移植的可靠保证,同时也是 Java 程序的安全检验引擎(还进行安全检查)。

JVM 是 编译后的 Java 程序(.class文件)和硬件系统之间的接口 ( 编译后:javac 是收录于 JDK 中的 Java 语言编译器。该工具可以将后缀名为. java 的源文件编译为后缀名为. class 的可以运行于 Java 虚拟机的字节码。)


JVM architecture:
技术分享图片
图片摘自 http://javapapers.com/java/java-garbage-collection-introduction/

JVM = 类加载器 classloader + 执行引擎 execution engine + 运行时数据区域 runtime data area
classloader 把硬盘上的class 文件加载到JVM中的运行时数据区域, 但是它不负责这个类文件能否执行,而这个是 执行引擎 负责的。


classloader

作用:装载.class文件
classloader 有两种装载class的方式 (时机):
1. 隐式:运行过程中,碰到new方式生成对象时,隐式调用classLoader到JVM
2. 显式:通过class.forname()动态加载

双亲委派模型(Parent Delegation Model):

类的加载过程采用双亲委托机制,这种机制能更好的保证 Java 平台的安全。
该模型要求除了顶层的Bootstrap class loader启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承(Inheritance)的关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。每个类加载器都有自己的命名空间(由该加载器及所有父类加载器所加载的类组成,在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类)

双亲委派模型的工作过程为:

1.当前 ClassLoader 首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,
等下次加载的时候就可以直接返回了。

2.当前 classLoader 的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到 bootstrap ClassLoader.
3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。

使用这种模型来组织类加载器之间的关系的好处:
主要是为了安全性,避免用户自己编写的类动态替换 Java 的一些核心类,比如 String,同时也避免了重复加载,因为 JVM 中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载就是不同的两个类,如果相互转型的话会抛java.lang.ClassCaseException.

类加载器 classloader 是具有层次结构的,也就是父子关系。其中,Bootstrap 是所有类加载器的父亲。如下图所示:技术分享图片
Bootstrap class loader: 父类
当运行 java 虚拟机时,这个类加载器被创建,它负责加载虚拟机的核心类库,如 java.lang.* 等。例如 java.lang.Object 就是由根类加载器加载的。需要注意的是,这个类加载器不是用 java 语言写的,而是用 C/C++ 写的。
Extension class loader:
这个加载器加载出了基本 API 之外的一些拓展类。
AppClass Loader:
加载应用程序和程序员自定义的类。

除了以上虚拟机自带的加载器以外,用户还可以定制自己的类加载器(User-defined Class Loader)。Java 提供了抽象类 java.lang.ClassLoader,所有用户自定义的类加载器应该继承 ClassLoader 类。

这是JVM分工自治生态系统的一个很好的体现。

http://www.importnew.com/6581.html


执行引擎

作用: 执行字节码,或者执行本地方法

runtime data area

JVM 运行时数据区 (JVM Runtime Area) 其实就是指 JVM 在运行期间,其对JVM内存空间的划分和分配。JVM在运行时将数据划分为了6个区域来存储。

程序员写的所有程序都被加载到运行时数据区域中,不同类别存放在heap, java stack, native method stack, PC register, method area.

下面对各个部分的功能和存储的内容进行描述:

技术分享图片

1、PC程序计数器:一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器, NAMELY存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。Java 的多线程机制离不开程序计数器,每个线程都有一个自己的PC,以便完成不同线程上下文环境的切换。

2、java虚拟机栈:与 PC 一样,java 虚拟机栈也是线程私有的。每一个 JVM 线程都有自己的 java 虚拟机栈,这个栈与线程同时创建,它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

3、本地方法栈:与虚拟机栈的作用相似,虚拟机栈为虚拟机执行执行java方法服务,而本地方法栈则为虚拟机使用到的本地方法服务。

4、Java堆:被所有线程共享的一块存储区域,在虚拟机启动时创建,它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配。

Java堆在JVM启动的时候就被创建,堆中储存了各种对象,这些对象被自动管理内存系统(Automatic Storage Management System,也即是常说的 “Garbage Collector(垃圾回收器)”)所管理。这些对象无需、也无法显示地被销毁。

JVM将Heap分为两块:新生代New Generation和旧生代Old Generation

Note:

  • 堆在JVM是所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也是new开销比较大的原因。
  • 鉴于上面的原因,Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间,这块空间又称为TLAB
  • TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效

5、方法区
方法区和堆区域一样,是各个线程共享的内存区域,它用于存储每一个类的结构信息,例如运行时常量池,成员变量和方法数据,构造函数和普通函数的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。当开发人员在程序中通过Class对象中的getName、isInstance等方法获取信息时,这些数据都来自方法区。

方法区也是全局共享的,在虚拟机启动时候创建。在一定条件下它也会被GC。这块区域对应Permanent Generation 持久代。 XX:PermSize指定大小。

6、运行时常量池
其空间从方法区中分配,存放的为类中固定的常量信息、方法和域的引用信息。


GC

javapapers
Java garbage collection is an automatic process to manage the runtime memory used by programs. By doing it automatic JVM relieves the programmer of the overhead of assigning and freeing up memory resources in a program.
java 与 C语言相比的一个优势是,可以通过自己的JVM自动分配和回收内存空间。

何为GC?
垃圾回收机制是由垃圾收集器Garbage Collection GC来实现的,GC是后台的守护进程。它的特别之处是它是一个低优先级进程,但是可以根据内存的使用情况动态的调整他的优先级。因此,它是在内存中低到一定限度时才会自动运行,从而实现对内存的回收。这就是垃圾回收的时间不确定的原因。

为何要这样设计:因为GC也是进程,也要消耗CPU等资源,如果GC执行过于频繁会对java的程序的执行产生较大的影响(java解释器本来就不快),因此JVM的设计者们选着了不定期的gc。

GC有关的是: runtime data area 中的 heap(对象实例会存储在这里) 和 gabage collector方法。
程序运行期间,所有对象实例存储在运行时数据区域的heap中,当一个对象不再被引用(使用),它就需要被收回。在GC过程中,这些不再被使用的对象从heap中收回,这样就会有空间被循环利用。
GC为内存中不再使用的对象进行回收,GC中调用回收的方法--收集器garbage collector. 由于GC要消耗一些资源和时间,Java 在对对象的生命周期特征(eden or survivor)进行分析之后,采用了分代的方式进行对象的收集,以缩短GC对应用造成的暂停。

在垃圾回收器回收内存之前,还需要一些清理工作。
因为垃圾回收gc只能回收通过new关键字申请的内存(在堆上),但是堆上的内存并不完全是通过new申请分配的。还有一些本地方法(一般是调用的C方法)。这部分“特殊的内存”如果不手动释放,就会导致内存泄露,gc是无法回收这部分内存的。
所以需要在finalize中用本地方法(native method)如free操作等,再使用gc方法。显示的GC方法是system.gc()

垃圾回收技术

方法一:引用计数法。简单但速度很慢。缺陷是:不能处理循环引用的情况。
方法二:停止-复制(stop and copy)。效率低,需要的空间大,优点,不会产生碎片。
方法三:标记 - 清除算法 (mark and sweep)。速度较快,占用空间少,标记清除后会产生大量的碎片。

JAVA虚拟机中是如何做的?
java的做法很聪明,我们称之为"自适应"的垃圾回收器,或者是"自适应的、分代的、停止-复制、标记-清扫"式垃圾回收器。它会根据不同的环境和需要选择不同的处理方式。

heap组成

由于GC需要消耗一些资源和时间的,Java在对对象的生命周期特征进行分析后,采用了分代的方式来进行对象的收集,即按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停.
heap 的组成有三区域/世代:(可以理解随着时间,对象实例不断变换heap中的等级,有点像年级)

  1. 新生代 Young Generation

    1. Eden Space 任何新进入运行时数据区域的实例都会存放在此
    2. S0 Suvivor Space 存在时间较长,经过垃圾回收没有被清除的实例,就从Eden 搬到了S0
    3. S1 Survivor Space 同理,存在时间更长的实例,就从S0 搬到了S1
  2. 旧生代 Old Generation/tenured
    同理,存在时间更长的实例,对象多次回收没被清除,就从S1 搬到了tenured

  3. Perm 存放运行时数据区的方法区

Java 不同的世代使用不同的 GC 算法。

  1. Minor collection:
    新生代 Young Generation 使用将 Eden 还有 Survivor 内的数据利用 semi-space 做复制收集(Copying collection), 并将原本 Survivor 内经过多次垃圾收集仍然存活的对象移动到 Tenured。
  2. Major collection 则会进行 Minor collection,Tenured 世代则进行标记压缩收集。

技术分享图片
To note that:
这个搬运工作都是GC 完成的,这也是garbage collector 的名字来源,而不是叫garbage cleaner. GC负责在heap中搬运实例,以及收回存储空间。

GC工作原理

JVM 分别对新生代和旧生代采用不同的垃圾回收机制

何为垃圾?

Java中那些不可达的对象就会变成垃圾。那么什么叫做不可达?其实就是没有办法再引用到该对象了。主要有以下情况使对象变为垃圾:
1.对非线程的对象来说,所有的活动线程都不能访问该对象,那么该对象就会变为垃圾。
2.对线程对象来说,满足上面的条件,且线程未启动或者已停止。

例如: 
(1)改变对象的引用,如置为null或者指向其他对象。 
   Object x=new Object();//object1 
   Object y=new Object();//object2 
   x=y;//object1 变为垃圾 
   x=y=null;//object2 变为垃圾 

(2)超出作用域 
   if(i==0){ 
      Object x=new Object();//object1 
   }//括号结束后object1将无法被引用,变为垃圾 
(3)类嵌套导致未完全释放 
   class A{ 
      A a; 
   } 
   A x= new A();//分配一个空间 
   x.a= new A();//又分配了一个空间 
   x=null;//将会产生两个垃圾 
(4)线程中的垃圾 
   class A implements Runnable{   
     void run(){ 
       //.... 
     } 
   } 
   //main 
   A x=new A();//object1 
   x.start(); 
   x=null;//等线程执行完后object1才被认定为垃圾 
   这样看,确实在代码执行过程中会产生很多垃圾,不过不用担心,java可以有效地处理他们。

JVM中将对象的引用分为了四种类型,不同的对象引用类型会造成GC采用不同的方法进行回收:
(1)强引用:默认情况下,对象采用的均为强引用
(GC不会回收)
(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用
(只有在内存不够用的情况下才会被GC)
(3)弱引用:在GC时一定会被GC回收
(4)虚引用:在GC时一定会被GC回收

载请注明出处http://segmentfault.com/blog/exploring/









































以上是关于java中垃圾回收机制的原理的主要内容,如果未能解决你的问题,请参考以下文章

Java垃圾回收机制的工作原理

Java面试题49 垃圾回收的优点和原理。并考虑2种回收机制。

5、垃圾回收机制

Java垃圾回收机制是啥?

JAVA垃圾回收的工作原理是啥?

深入理解V8的垃圾回收原理