谈谈java的内存模型

来源:互联网 发布:金十数据日历桌面 编辑:程序博客网 时间:2024/05/21 17:22

1. jvm

java中负责管理内存和垃圾回收等工作的东西:jvm我们称之为java虚拟机。简单来讲,就是jvm帮你管理你的内存分配与回收,不需要你去像c++中一样需要自己delete内存。

    需要注意的几点:
1. jvm只负责与内存相关的资源的管理,其他的不属于jvm的范畴,需要你自己去控制。
e.g.文件的读取操作中流的开启和关闭、数据库连接的释放等等,这些jvm不会管理,需要你自己显式地控制,否则会出现资源消耗的情况,最终导致系统崩溃。方法是:可以在finally块中显式地释放,无论如何finally块都会在内存资源被jvm回收之前获得执行,当然,这并不意味着可以明确gc的执行时间,gc是不能被程序员控制的!

2. 有了jvm的管理内存泄露的情况依然有可能会发生
很多初学者认为java自己管理gc,那么完全不必要担心内存泄露的情况发生,这是完全错误的认识。
我们看下面的一段模拟出栈的pop代码就发生了内存泄露:
public class Stack {Object[] stack;int top;public void pop(){top--;}}
虽然top--之后我们不能再访问到已经被pop的元素,但是由于实际的对象仍然在stack数组中保存了引用,因此不会被gc回收,这样这段内存就处于这种状态:访问不到也不能回收,这就是内存泄露;一次两次是没有问题,但是当调用的次数很多的时候,内存会被一点一点得耗尽,直到系统崩溃。
正确的做法是:将stack数组的该引用指向null,让gc在随后回收实际的对象
public void pop(){stack[top] = null;top--;}


2. 堆和栈

jvm将java中的基本类型变量和对象的存储分为两个区域:堆区和栈区。
a. 栈区:
存放函数的临时变量,包括java的基本数据类型变量和指向对象的引用变量(这个比较难理解,说白了就是new对象的时候等号前面的那个变量)
b. 堆区:
存放java中的实际的对象(包括数组,数组也是一种对象,显然创建一个数组也是通过new出来的)。堆区对于程序员是不透明的,jvm不允许程序员直接访问堆区中的对象,而只能通过栈区中的引用对象通过引用的方法访问实际的对象,即从栈区的引用变量有一个指向堆区中的实际对象的“指针“。

jvm为什么要设立栈区和堆区之分?
这个问题我的理解是:
1. 为了效率(efficiency)。jvm的内存管理其实基于这样的一个事实:java程序的大部分对象或者变量都是短命的(即仅仅处于年轻代),而只有少量的对象或变量会进入持久代,还有极少数的对象生命周期更长,进入到了老年代。因此,设立栈区存放那些临时变量和基本数据类型,而在堆区中的对象则会进一步分成年轻代、持久代、老年代等,用不同的策略来实行高效的gc。
2. 为了安全。众所周知,c++中的指针式初学者的一个难题,很多有经验的码农遇到指针依然会出错。由于c++不是很熟,所以不能做过多的评价,不过记得空指针和野指针的问题应该是c++中的很容易出现的问题。在java中,通过引用变量的方式避免了类似的问题的发生,jvm拒绝程序员直接访问堆区中的对象,而必须通过栈区中的引用变量访问。

3. gc的机制

简单理解就是:引用标记算法,即gc计算堆中的对象是否有指向它的引用,如果有,那么就不会回收这个对象,否在就将对象放入到待回收区域,等待回收。如果在回收执行之前,对象又被重新引用了,那么它又会重新进入活动对象区,不会被回收了。
首先从stack和静态分配区的ref查找heap中对应的对象,如果没有ref就回收它
我们来看一个例子:
class X2{    public X2 x;    public static void main(String[] args){         X2 x2=new X2();        X2 x3=new X2();        x2.x=x3;        x3.x=x2;        x2=new X2();        x3=x2;        doComplexStuff();}}after line 9 runs,how many objects are eligible for garbage collection?A.0  B.1  C.2  D.3  E.4

在第8行之前,它的内存模型是这样的:

在第9行之后,它的内存模型变为:

于是之前的两块内存空间不再有ref,可以被gc了,所以答案应该是C:2个。

原创粉丝点击