java gc回收堆还是栈_JVM内存管理、堆、栈,内存溢出、垃圾回收[Java程序性能优化] | 学步园...

---链接:

http://wade6.iteye.com/blog/1842907(ok)

http://blog.sina.com.cn/s/blog_701c951f0100n1sp.html (good)

http://java-mzd.iteye.com/blog/848635(堆、栈)

http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html(堆内存、栈内存)

http://jelly-x.iteye.com/blog/1120406

https://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/

http://coder-xpf.iteye.com/blog/2144455

===Java内存管理:堆、栈

---Java内存管理

在解决java内存溢出问题之前,需要对jvm(java虚拟机)的内存管理有一定的认识。

jvm管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。

其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGen space区域,Class需要存储的内容主要包括方法和静态属性。堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理.Java栈跟大多数

--栈:

JVM栈是程序运行时单位,决定了程序如何执行,或者说数据如何处理;Java栈保存方法的局部变量(基本类型的变量or对象的引用变量),部分结果,并参与方法的调用和返回。

在Java中,一个线程都会有一个JVM栈与之对应,JVM栈用来存放该线程的执行逻辑。

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。

可能的异常:

1.StackOverflowError:请求的栈深度大于最大可用的栈深度。函数嵌套调用的次数由栈的大小决定

2.OutOfMemoryError:动态扩展Java栈的过程中,没有足够的内存空间来支持栈的扩展

当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。同时,因为变量被释放,该变量对应的对象也就没有引用变量指向它,也就变成了可以被gc回收的垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!

因此我们可以知道成员变量与局部变量的区别:

1.局部变量,在方法内部声明,当该方法运行完时,内存即被释放。

2.成员变量,只要该对象还在,哪怕某一个方法运行完了,还是存在。

3.从系统的角度来说,声明局部变量有利于内存空间的更高效利用(方法运行完即回收)。

4.成员变量可用于各个方法间进行数据共享。

--堆:

java的堆是一个运行时的数据区,用来存储数据的单元,存放通过new关键字新建的对象和数组,对象从中分配内存。

在堆中声明的对象,是不能直接访问的,必须通过在栈中声明指向该对象的引用变量来调用。

在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

--堆与栈的比较:

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。

堆内存用于存放由new创建的对象和数组。

从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的

在函数代码块中定义一个变量时, java就在栈中为这个变量分配内存空间,当超过变量的作用域后, java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理。

堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢;

栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的无灵活性。

--从数据共享的角度:

1).在单个线程类,栈中的数据可共享

例如我们定义:

int a=3;

int b=3;

编译器先处理int a=3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b=3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。

而如果我们定义:

Integer a=new Integer(3);//(1)

Integer b=new Integer(3);//(2)

这个时候执行过程为:在执行(1)时,首先在栈中创建一个变量a,然后在堆内存中实例化一个对象,并且将变量a指向这个实例化的对象。在执行(2)时,过程类似,此时,在堆内存中,会有两个Integer类型的对象

--参数传递:

有了以上关于栈和堆的种种了解后,我们很容易就可以知道值传递和引用传递的真相:

1.程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

在运行JVM栈中,基本类型和引用的处理是一样的,都是传值,如果是传引用的方法调用,当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。

--java引用变量:

引用:

可以对堆中的对象进行操作。在某函数中,当创建一个对象,该对象被分配在堆中,通过该对象的引用才能对该对象进行操作。

如函数体内:StringBuffer sb = new StringBuffer("Hello world!");则:局部变量sb分配在栈上,对象的实例分配在堆上;sb指向实例所在的堆空间,通过sb可以操作该实例,sb是实例的引用

强引用:

强引用可以直接访问目标对象

强引用所指向的对象在任何时候不会被系统回收(jvm抛出oom异常,也不会回收强引用所指向的对象)

强引用可能导致内存泄漏

软引用:

一个持有软引用的对象,jvm会根据当前堆的使用情况来判断何时回收

弱引用:

在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。

虚引用:

随时可能被回收

===java的JVM内存溢出、垃圾回收

问题:java有垃圾回收机制,为什么还会出现内存溢出

解答:因为java的这个垃圾回收机制是不定时的,不可控的。当回收还没开始时,内存就超出了上限,就会出现内存溢出的情况。这里面还涉及到了JVM的一些内存管理和参数配置。

问题:Java什么时候会出现内存溢出oom

解答:

Java应用程序在运行时,首先会被分配-Xms指定的内存大小,并尽可能尝试在这个空间段内运行程序。

当-Xms指定的内存大小确实无法满足应用程序时,JVM才会向操作系统申请更多的内存,直到内存大小达到-Xmx指定的最大内存为止。

若内存所需大小超过-Xmx的值,则抛出OutOfMemoryError异常。

简而言之:应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存,就会oom了。

---JVM参数配置

Java应用程序用-Xmx参数指定最大堆内存。

Java应用程序使用JVM参数-Xms设置最小堆空间。

如果-Xms的数值较小,那么JVM为了保证系统尽可能在指定内存范围内运行,就会更加频繁地进行GC操作,以释放失效的内存空间。这样会增加Minor GC和Full GC的次数,对系统性能会产生一定的影响。

Java应用程序用参数-Xmn,-XX:MaxNewSize设置新生代的大小,设置较大的新生代同时也会减小老年代的大小。新生代的大小一般设置为整个堆空间的1/4到1/3左右。

设置持久代大小,用参数-XX:MaxPermSize。持久代不属于堆的一部分,持久代大小直接决定系统可以支持多少个类定义和多少常量

设置线程栈的大小,用参数-Xss

---垃圾回收GC

在Java中,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。

在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。

---内存泄漏、内存溢出

--下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。

Vector v=new Vector(10);

for (int i=1;i<100; i++){

Object o=new Object();

v.add(o);

o=null;

}

//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。

--

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。

内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。

内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

1、堆内存溢出(outOfMemoryError:java heap space)

2、方法区内存溢出(outOfMemoryError:permgem space)

3、线程栈溢出(java.lang.StackOverflowError)

4、java.lang.OutOfMemoryError: unable to create new native thread

内存分配越多,所能创建线程数越少

线程数太多,系统运行能效反而下降

--解决之道

1、尽早释放无用对象的引用

2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域

3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收

4、避免在循环中创建对象

5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

参考:[Java程序性能优化] --- 清华大学出版社

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值