JVM内存管理基础

来源:互联网 发布:手机恢复数据免费 编辑:程序博客网 时间:2024/06/08 06:18

内存管理基础

From 深入分析JavaWeb

一个Java进程的哪些部分需要分配内存


Java堆

  用于存储Java对象的区域,需要分配内存,堆大小在JVM启动时就向操作系统申请了。通过-Xmx和-Xms来控制大小,前者表示最大的堆的大小,后者表示初始化的堆的大小。

  JVM管理着堆内存,对象创建由应用程序控制,而对空间的释放由GC垃圾收集器来完成。根据GC算法的不同,回收的时机也不尽相同。

线程

  JVM运行程序的实体就是线程。所以需要一些内存来存储线程运行所需要的数据。JVM会为每一个线程构建一个线程栈,通常在256K~756KB之间。

类和类加载器

Class对象和加载Class的ClassLoader对象需要空间存储,他们被存储在称为PermGen区,永生代。
  PermGen永生代的内存回收需要满足3个条件:

1,在Java堆中没有对表示该类加载器的Java.lang.ClassLoader对象的引用。2,Java堆没有对表示类加载器的类的任何java.lang.Class对象的引用。3,在Java堆上该类加载器加载的任何类的所有对象都不在存活。 


  所以Bootstrap ClassLoader、APPClassLoader和ExtClassLoader都不满足这些条件,在程序运行时无论是系统类还是程序加载的类都不会在运行时释放。

NIO

  在java1.4以后加入了NIO(new io),引入了一种基于通道和缓冲区来执行IO的方式,NIO使用java.nio.ByteBuffer,allocateDirect()方法分配内存,这种方法分配的内存是本机的内存,而不是JVM最开始的-Xmx内存。这类方法直接在底层调用os:malloc()方法。

本地调用JNI

  Java Native Interface,Java本地调用c/c++方法,如一些文件操作,网络IO和其他一些系统调用会占用一定的内存。


JVM内存体系结构


PC寄存器

  PC寄存器应该说是一种数据结构更合适,用于保存当前正常执行的程序的内存地址。java是多线程的,所以线程可能不会一直线性执行下去,多个线程交叉执行的时候,被中断的线程执行到哪里的内存地址需要保存起来,以便中断结束后继续执行。

Java栈-栈帧

  JVM为每一个线程分配一个栈,而在这个栈中又会形成多个栈帧,一个栈帧对应与一个方法调用,正在执行的方法放在栈帧的顶部,这个地址也是PC寄存器指向的地址,在方法内部调用方法就会形成新的栈帧继续放在顶部。而在栈帧返回后,将数据返回给前一个栈帧,这就是退栈的过程。由于java栈与java线程对应起来的,所以数据不是共享的,故不用关心数据一致性过程,也不会有同步锁的问题。

堆-Java对象存储的地方

  堆是存储Java对象的地方,是核心存储区域,每一个存储在堆中的对象,都会是这个对象的类的一个副本,继承了从他父类开始的所有非静态属性。堆是线程共享的,所有可能需要通过同步加锁来保持一致性问题。

方法区

  常量池,域,方法数据,方法体,构造函数体,类的专用方法,实例初始化,接口初始化。这个方法区实际上也是java堆的一部分,这个区叫做“PermGen”。方法区是逻辑上的独立而已!

  运行时常量池,Runtime Constant Pool代表运行每个Class文件的常量表。编译器的数字常量、方法等。

本地方法区

  JVM运行Native方法时准备的空间,因为通常Native方法都是用C实现的,所以本称为C栈。


内存回收检测

Garbage Collection:垃圾收集器,主要完成2部分功能,一是检测出垃圾对象,二是释放垃圾对象占用的空间。

  当一个对象不再被其他活动对象所引用的时候,那么这个对象就可以被回收了,活动对象值得是能有一条到根对象集合的对象。如下图所示:例如下图的g和h对象,虽然h对象被g对象引用,但是g对象不能达到根对象节点,所以这2个对象是非活动对象,可以被垃圾收集器回收。


根对象集合的内容

  • 在方法中局部变量区的对象的引用:比如定义在play()方法的局部Date对象,这个对象直接存储栈帧的局部变量区中。

  • 在java操作栈中的对象引用:这些对象在java操作栈中,肯定是包含在根对象中的。

  • 在常量池中的对象引用:每一个类都包含一个常量池,通常会有很多对象引用,如表示类名字的字符串就在此。

  • 本地方法持有的对象引用:在调用native方法,但这些对象还没有被释放时。

  • 类的Class对象:每当JVM加载一个类的时候,都会创建代表这个类的Class对象,同样是放在堆中。而这个类当不再被使用时,也同样可以被回收。


Hotspot分代垃圾收集算法

设计思路

  将对象根据寿命的长短来分类,分成Young和Old组,最开始新建的对象都放在Young区,经过数次垃圾收集后依然存在则应该将其放入Old区,Old区由于是寿命较长的对象,垃圾收集并不是那么频繁,这样就提高了垃圾回收的效率。


Java将堆分成的3个部分,Young区,Old区和Perm区。

  • Young区分为Eden和两个Survivor区,Survivor分为From区和To区。最开始所创建的对象都在Eden区,当Eden区满以后触发minor GC将Eden区仍然存活的对象复制到To区,然后将Survivor的From区对象也都复制存放到To区,这样From区为空,此时将From区重命名为To区,将To区重命名为From区。始终保证有一个Survivor区为空。(书上说是复制到其中一个Survivor区,我结合Node.js的知识和FromTo区,自己猜测阐述了上述说法,不一定正确,但是这样理解比较容易些吧,不然复制的时候怎么知道要复制到哪里呢?)
  • Old区:存放的是当Survivor区满的时候触发minor GC操作后仍然存活的对象;当Eden区满且Survivor区容不下这些对象的时候也会放到Old区。如果Old区满了,那就触发Full GC大清理对象。
  • PermGen区:主要是用来存放类的Class对象和ClassLoader对象,如果一个类被频繁加载,也可能导致PermGen满,此时会触发Full GC。

  如果使用不同的ClassLoader实例加载同一个类会怎么样,会不会导致JVM的PermGen(永久代)n区无限增大??答案是否定的,why,因为ClassLoader对象也是对象,在没有被持有引用的时候,也会被JVM回收。值得注意的是,被ClassLoader加载的类的字节码会一直保存在JVM的PermGen中,这个数据一般是在Full GC的时候才会被回收,所以,如果应用大量使用动态类加载。Full GC又不是太频繁,也要注意PermGen的大小,防止内存溢出。


堆建议


Sun公司给的堆大小建议是Young区占总堆的1/4,而Survivor区占整个Young区的1/8。

存在三类垃圾收集算法:

  • Serial Collector
  • Parallel Collector
  • CMS Collector

不会讲。讲不好。不讲了。

0 0
原创粉丝点击