认识java的内存机制。
- 在Java里内存分成两种:一种是栈内存,一种是堆内存。
栈内存
-
在函数中定义的一些基本类型的局部变量和对象的引用变量都是在函数的栈内存中分配。
- 比如下面的代码中,变量i是基本类型的局部变量,变量blue是引用变量,都在栈内存中分配。
public void test(){ int i = 0; } BlueCola blue = new BlueCola();
-
当在一段代码块中使一个变量有值时,Java 就在栈中为这个变量分配内存空间
-
当局部变量的作用域不再被使用后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
-
注意:
- 成员变量会随着对象在堆中空间的开辟而存储在堆中 。
堆内存
-
Java 中的堆是 JVM 管理的最大的一块内存空间,主要用于存放Java类的实例对象,其被划分为两个不同的区域,新生代 ( Young )与老年代(Old)。
-
其中新生代又被分为三个区域
- Eden
- From Survivor
- To Survivor
-
堆内存用来存放用
new
操作符创建的对象和数组,以及引用对象对应的成员变量。 -
比如在如下代码中
new BlueCola();
操作产生了一个对象,这个对象就在堆内存中。BlueCola blue = new BlueCola();
-
在创建了一个对象或数组之后,还可以声明一个局部变量,让栈中的这个变量的取值等于该数组或对象在堆内存中的首地址,栈中的这个变量就成了该数组或对象的引用变量,可以来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。
-
引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。但给数组和对象分配的内存不会被释放。
-
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
垃圾回收
-
数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。
-
分代垃圾回收机制,是基于这样一个事实:不同的对象的声明周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代、持久代。JVM将堆内存分为Eden、Survivor和Tenured/Old空间。
- 年轻代
- 所有新生成的对象首先都是放在Eden区。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代”区域存放满对象后,就将对象存放到年老代区域。
- 年轻代
-
年老代
- 在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象,就将会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,我们就需要启动Major GC和Full GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。
-
持久代
- 用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响。
-
GC
- Minor GC:用于清理年轻代区域。Eden区满了就会触发一次Minor GC。清理无用对象,将有用对象复制到Survivor1、Survivor2区中。
- Survivor1、Survivor2这两个区,大小空间也相同,同一时刻Survivor1和Survivor2只有一个在用,一个为空。
- Eden所有对象通过算法过一遍,通过引用技术或者引用可达法算一遍。把无用的清除掉,把有用的留下。同时采用复制算法,把留下的有用的一下复制到Survivor1区或Survivor2区
- Major GC:用于清理老年代区域。
- Full GC:用于清理年轻代、年老代区域。成本较高,会对系统性能产生影响。
- Minor GC:用于清理年轻代区域。Eden区满了就会触发一次Minor GC。清理无用对象,将有用对象复制到Survivor1、Survivor2区中。
-
概括GC的运作(垃圾回收过程)
- 新创建的对象,绝大多数都会存储到Eden中。
- 当Eden满了(达到一定比例)不能创建新的对象,则触发垃圾回收(GC),将无用对象清理掉,然后剩余对象复制到某个Survivor中,如
Survivor1,同时清空Eden区。 - 当Eden区再次满了,会将S1中的不能清空的对象存到另外一个Surivor中,如Survivor2,此时Survivor1被清空。同时将Eden区中的不能清空的对象,再次复制到Survivor1中。这一过程Eden和Survivor1均被清空过。
- 重复多次(默认15次)Survivor中没有被清理的对象,则会复制到老年代Old(Tenured)区中。
- 为什么是重复15次,因为每一种重复状态都会用对象头的4位来记录,4位最大可以记录16种状态,算上无重复的状态,还可以记录15次。
- 当Old区满了,则会触发一个一次完整的垃圾回收(Full GC),之前新生代的垃圾回收称为(minorGC)。
-
有以下四种情况会导致Full GC,在JVM调优中需要注意。
- 年老代(Tenured)被写满
- 持久代(Perm)被写满
- System.gc()被显式的调用(程序建议GC启动,不是调用GC)
- 上一次GC之后Heap的各域分配策略动态变化。
简单的比较
- 堆主要用来存放对象的
- 栈主要是用来执行程序的。
一个完整的Java程序运行过程会涉及以下内存区域:
寄存器:
- JVM内部虚拟寄存器,存取速度非常快,程序不可控制。
栈:
主要用来执行程序,存取速度快,大小和生存期必须确定,缺乏灵活性
- 保存局部变量的值:
- 1.保存基本数据类型的值;
- 2.保存引用变量,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。
- 栈的内存管理是通过栈的"后进先出"模式来实现的。
- 栈内存是线程私有的,他的生命周期和线程相同。
- 栈内数据共享。
- 存放基本类型的变量数据,局部变量,和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
堆:
主要用于存放对象,存取速度慢,可以运行时动态分配内存,生存期不需要提前确定
- 用来存放动态产生的数据,比如new产生的对象,数组。
- 注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。
- 因为同一个类的对象拥有各自的成员变量,存储在各自的堆中。
- 他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
- java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候被创建。
- java堆是垃圾收集的主要区域,现在垃圾收集一般是按照分代收集的的所以java堆还可以细分为:新生代、老年代;再细分就是Eden空间 、From Survivor 空间、To Survivor 空间。
方法区:
用来存放已被加载的类的信息、常量、静态变量、即时编译器编译后的代码(在java中static的作用就是说明该变量,方法,代码块是属于类的还是属于实例的)
常量池:
JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。 - 包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用。
- 池中的数据和数组一样通过索引访问。
- 由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用
- 存放字符串常量和基本类型变量,比如String str=”gxx”; 实际上”gxx”是在常量池里边。
- 常量池是在方法区(Method Area)中而不是堆内存中。
都有哪些基本类型使用了常量池呢?
- Java的8种基本类型
Byte
,Short
,Integer
,Long
,Character
,Boolean
,Float
,Double
, 除Float
和Double
以外, 其它六种都实现了常量池。
注意
- 但是它们只在大于等于-128并且小于等于127时才使用常量池。而如果大于127 或小于-128 则不会使用常量池所以会直接在堆内存中创建对象。