您的位置:首页 > 其它

JVM内存模型&内存溢出&垃圾回收

2017-04-25 14:28 260 查看
栈是线程私有的,是方法执行时分配的内存,以栈桢为分配单位;栈桢中以方法的局部变量表为主,局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。其中64 位长度的long 和double 类型的数据会占用2 个局部变量空间(Slot),其余的数据类型只占用1 个。

疑问:复杂对象也是由基本对象构成的,为什么不能在编译时确定内存?

答:复杂对象会分配一个reference对象

堆是所有线程共享的空间,在虚拟机启动时创建,唯一的作用是存放对象实例,是垃圾收集器管理的主要区域

方法区其实和方法无关,是堆的逻辑部分,主要存一些不变的信息,如果加载的类、常量、静态变量等;这个区域很少会进行垃圾回收,回收目标主要是针对常量池的回收和对类型的卸载。运行时常量池是方法区的一部分,可以在运行时动态改变,用string的intern方法。

直接内存一般是native方法分配的,在JVM内存之外的空间,受机器总内存限制。和JVM的交互是由存储在JVM里的DirectByteBuffer 直接引用来处理,这部分也有可能引起堆外(即JVM之外)的内存溢出异常。



内存的操作过程:

Object obj = new Object();

假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现。而“new
Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance
Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

-------------------内存溢出------------------

StackOverflowError是栈溢出异常,而OutOfMemoryError是堆内存不足异常

通过参数-XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump 出当前的内存堆转储快照以便事后进行分析。

导致内存泄露的代码可能是:

1、静态变量,如一个集合中的元素,如果元素被加到集合后设置为NULL,那么集合会仍然持有该元素

Static Vector v = new Vector(10); 
for (int i = 1; i<100; i++) 

Object o = new Object(); 
v.add(o); 
o = null; 
}

在量宽信息工作期间,就遇到一个同学写的内存泄露的例子:展现策略列表,其中包括策略的市值,这就意味着后台需要取得这个策略持仓对应的行情来计算。这位同学将取得的行情放在一个static map中,导致这个MAP所占的内存越来越大。仔细分析,其实每次取得的行情都没有缓存的必要,因为行情是实时变化的,所以这些行情数据的生命周期应该是一次请求的时间而已,而不应该是整个应用的生命周期。

2、单例模式的对象在整个生命周期都会存在,如果它持有其他对象的引用较多,是不会被回收的;

3、各种连接,如数据库连接不关闭

4、A对象引用B对象,A对象的生命周期(t1-t4)比B对象的生命周期(t2-t3)长的多。当B对象没有被应用程序使用之后,A对象仍然在引用着B对象。这样,垃圾回收器就没办法将B对象从内存中移除,从而导致内存问题,因为如果A引用更多这样的对象,那将有更多的未被引用对象存在,并消耗内存空间。

解决内存溢出:

1、可以在内存溢出时使用JDK自带的命令,注意最后一个数字是JAVA进程的PID,将内存快照DUMP到一个二进制文件中

jmap -dump:format=b,file=heap.bin 31342

2、使用ECLIPSE提供的memory analyze来分析

-------------------------垃圾回收-------------------

1、软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

2、弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器

新生代、老年代、永久代。新生代回收较为频繁,一般采用标记-清除法回收,老年代相对频次较低,一般采用标记-整理法回收,永久代基本不回收,但随着现在的反射、动态加载越来越多,对此区域回收就变得有必要了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: