`
orange5458
  • 浏览: 347761 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

JVM内存管理

 
阅读更多

1.Java中哪些组件需要用到内存

1)堆

用于存储Java对象的内存区域,堆的大小在JVM启动时就一次向OS申请完成,通过-Xmx和-Xms两个选项来控制大小,前者表示堆的最大大小,后者表示堆的初始大小。一旦分配完成,堆的大小就将固定,不能在内存不够时再向OS重新申请,同时当内存空闲时也不能将多余的空间还给OS。

Java堆中内存空间的管理由JVM来控制,对象创建由Java应用程序控制,但是对象所占的空间释放由管理堆内存的垃圾收集器(GC)来完成。根据垃圾收集(GC)算法的不同,内存回收的方式和时机也会不同。

2)线程

JVM运行实际程序的实体是线程,当然线程需要内存空间来存储一些必要的数据。每个线程创建时JVM都会为它创建一个栈。

3)类和类加载器

它们也被存储在堆中,这个区域叫做永久区(PermGen区)。

JVM只会加载一个类到内存一次,并且JVM也不会对失效的类做卸载,通常一个类能够被卸载有如下一些条件需要被满足:

(1)堆中没有对表示该类加载器的java.lang.ClassLoader对象的引用。

(2)堆中没有对表示该类加载器的类的任何java.lang.Class对象的引用。

(3)堆上该类加载器加载的任何类的所有对象都不再存活。

4)NIO

NIO使用java.nio.ByteBuffer.allocateDirect()分配内存时使用的是本机内存而不是JVM堆上的内存。

直接ByteBuffer对象会自动清理本机缓冲区,但这个过程只能作为JVM堆GC的一部分来执行,因此它们不会自动响应施加在本机内存上的压力。GC仅在Java堆被填满,以至于无法为堆分配请求提供服务时发生,或者在Java应用程序中显示请求发生。

当前很多NIO框架都在代码中显示调用System.gc()来释放NIO持有的内存。但是这种方式会影响应用程序的性能,因为会增加GC的次数,可以通过设置-XX:+disableExplicitGC来控制System.gc()的影响。

5)JNI

会增加Java程序运行时的本机内存占用。

2.JVM内存结构

JVM是按照运行时数据的存储结构来划分内存结构的,JVM在运行Java程序时,将他们划分成几种不同格式的数据,分别存储在不同的区域,这些数据统一称为运行时数据(Running Data)。

JVM规范将运行时数据划分为6中:

1)PC寄存器

严格来说是一个数据结构,用于保存当前正在执行的程序的内存地址。

注:JVM规范只定义了Java方法需要记录指针信息,而对于Native方法,并没有要求记录执行的指针地址。

2)栈

每当创建一个线程时,JVM就会为这个线程创建一个对应的栈,栈中的每个数据单元称为栈帧,栈帧是与每个方法关联起来的,每运行一个方法就创建一个栈帧,每个栈帧会包含一些内部变量,操作栈和方法返回值等信息。

3)堆

堆是存储Java对象的地方,它是JVM管理Java对象的核心存储区域。

每一个存储在堆中的Java对象都会是这个对象的类的一个副本,它会复制包括继承自它父类的所有非静态属性。

堆是被所有Java线程所共享的,所以对它的访问需要注意同步问题,方法和对应的属性都需要保证一致性。

4)方法区

用于存储类结构信息的地方,在将一个class文件解析成JVM能识别的几个部分,这些不同的部分在这个class被加载到JVM时,会被存储在不同的数据结构中,其中的常量池,域,方法数据,方法体,构造函数,包括类中的专用方法等都存储在这个区域。

这个存储区域属于堆的一部分,通常称为永久区。被所有线程共享,可以通过参数设置它的大小。

这个存储区域的大小一般在程序启动后的一段时间内就是固定的了。但是假如项目中存在对类的动态编译,而且是同样一个类的多次编译,那么需要观察方法区的大小是否能满足存储。

虽然它存储的信息相对比较稳定,也不会频繁地被垃圾收集器(GC)扫描,但是仍然会被JVM的垃圾收集器(GC)管理。

5)本地方法区

为JVM运行Native方法准备的空间,和前面介绍的栈的作用类似,由于很多Native方法都是用C语言实现的,所以它通常又叫C栈。

使用这个区域的代码有:

(1)常规的Native方法。

(2)JIT编译技术把一些Java方法重新编译为Native代码。

6)运行时常量池

代表运行时class文件中的常量表。它包括几种常量:编译期的数字常量,方法或者域的引用。

它是方法区的一部分。

 3.JVM内存分配策略

JVM内存分配主要基于两种,分别是堆和栈。

栈的分配是和线程绑在一起的,栈的基本数据单元是栈帧,对应每个执行方法,用来保存参数,局部变量,中间计算过程和其他数据。

栈中主要存放一些基本类型的变量数据(int,short,long,byte,float,double,boolean,char)和对象句柄(引用)。存取速度比较快,仅次于寄存器,栈数据可以共享,内存空间在线程结束时自动回收。缺点是,存在栈中的数据大小与生存期必须是确定的,这也导致缺乏了其灵活性。

这些变量的在运行时需要存储空间,同时在执行指令时JVM也需要知道栈的大小,这些数据都会在Javac编译这段代码时就已经确定。

每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用程序所有的线程共享。

堆内存是自动初始化的,所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配的。也就是说在建立一个对象时两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在栈中分配的内存只是一个指向这个堆对象的引用而已。

堆是由垃圾收集器(GC)来负责的,优点是可以动态分配内存大小,生存期也不用事先告诉编译器,因为它是在运行时动态分配内存的,GC会自动收走不再使用的数据。缺点是存取速度较慢。

4.JVM内存回收策略

垃圾收集器(GC)的任务有:正确检测出垃圾对象,释放垃圾对象占用的内存空间。

1)如何检测垃圾

只要是某个对象不再被其它活动对象引用,那么这个对象就是垃圾对象。这里的活动对象指的是能够被一个根对象集合到达的对象。

根对象集合的内容有:

(1)方法中局部变量区中对象的引用

(2)Java操作栈中的对象引用

(3)常量池中的对象引用

(4)本地方法中持有的对象引用

(5)类的Class对象

5.垃圾收集算法

6.参考资料

《深入分析 Java Web 技术内幕》

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics