jvm的内存管理机制

java的内存管理机制

1 java的内存区域

java虚拟机运行时的数据区包括
运行时数据区:方法去,虚拟机栈 本地方法栈,堆 程序计数器java

程序计数器:

工做时经过改变这个计数器的值来选择下一条须要执行的字节码指令,好比循环,分支,跳转,处理异常,线程恢复程序员

为了线程切换后可以恢复到正确的位置,每一个线程都须要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,这类内村区域称为“线程私有”的内存web

此内存区域是惟一一个在java虚拟机规范中没有规定任何outofmemoryError的区域数组

虚拟机栈

也是线程私有,生命周期与线程相同,程序员常常讲的栈内存就是虚拟机栈中的局部变量表部分jvm

局部变量表中存放了各类基本数据类型,对象引用和returnAddress类型,long double会占据两个局部变量空间,其他只占一个,其须要的内存空间在编译期间完成分配svg

java虚拟机规范中,若是线程请求的栈深度大于虚拟机所容许的深度,会抛出stackOverflowError;虚拟机栈在动态扩展时,没法申请到足够内存,会抛出OutOfMemoryError工具

本地方法栈

与虚拟机栈不一样的地方在于:本地方法栈为使用到的Native方法服务,虚拟机栈为执行的java方法服务,也会抛出上面的两个异常布局

大多数应用中,堆是java虚拟机管理内存中最大的一块,被全部的线程共享,在虚拟机启动时建立。它的惟一目的就是存放对象实例,是垃圾收集器管理的主要区域spa

方法区

各个线程共享,存放已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据线程

2 java对象的内存布局

对象在内存中存储的布局能够分为3块区域:对象头,实例数据和对齐填充

对象头包括两部分信息:一、存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁等。另一部分是类型指针,jvm经过这个指肯定对象是哪一个类的实例。另外,若是对象是一个java数组,对象头中还必须有一块记录数组长度的数据,由于jvm从数组中的元数据没法肯定数组的大小

实例数据是对象真正存储的有效信息,是代码中所定义的各类类型的字段内容

对齐填充不是必要存在的,仅仅起着占位符的做用。对象的大小必须是8字节的整数倍。对象头正好是8的整数倍,当对象的实例数据没有对齐,就须要对齐填充来补全

3 对象的访问定位

主流的访问方式有句柄和直接指针两种

句柄

java堆分出一块内存做为句柄池,reference存储对象的句柄地址,句柄包含了对象的实例数据和类型数据的具体地址信息

直接指针

reference存储的直接就是对象地址

二者优缺点 2更快,1更稳定,对象被移动只需修改句柄中的实例数据指针,不要修改reference

4 outOfMemoryError异常

(1)java堆溢出

只要不断建立对象,而且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,在对象数量到达最大堆的容量限制后就会产生内存溢出异常

要解决这个区域的异常,通常的手段先经过内存映像工具(Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,分清楚是出现了内存泄漏仍是内存溢出

(2)虚拟机栈和本地方法栈溢出

两种异常:1 若是线程请求的栈深度大于虚拟机所容许的最大深度,将抛出StackOverFlowError。2 若是虚拟机在扩展栈时没法申请到足够的内存空间,则抛出OutOfMemoryErro异常

栈溢出实例

public class Demo2
{
public int stackLength =1;

public void stackLeak() {
    stackLength++;
    stackLeak();
}
public static void main(String[] args)
{
    Demo2 demo2 = new Demo2();
    try {
        demo2.stackLeak();
    }catch(Throwable e) {
        System.out.println("Stack length:"+demo2.stackLength);
        throw e;
    }
        
}

}

实验结果代表:在单个线程下,不管是因为栈帧太大仍是虚拟机栈容量过小,当内存没法分配的时候,虚拟机抛出的都是StackOverflowError

(3)方法区和运行时常量池溢出

首先说一个方法:String.intern(),它是一个native方法,它的做用是:若是字符串常量池中已经包含了一个等于此String对象的字符串,则返回表明池中这个字符串的String对象;不然,将此String对象包含的字符串添加到常量池中。

public static void main(String[] args) {

//利用list保持常量池的引用,避免Full GC 回收常量池的行为
   List<String> list = new ArrayList<>();
   int i = 0;
   while(true) {
       list.add(String.valueOf(i++).intern());
   }
}

上面的这个例子,会报OutOfMemoryError:PermGen space 可是1.7的jdk会将循环一直执行下去
1.7开始逐步去永生代,1.6的intern方法会先把首次遇到的字符串实例复制到永生代中,1。7只是在常量池中记录首次出现的实例引用,而不是复制这个实例。

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

(4) 本机直接内存溢出

DirectMemory容量可经过 -XX:MaxDirectMemorySize指定,若是不指定,则默认与JAVA堆最大值(-Xmx指定)相同

由DirectMemory致使的内存溢出,一个明显的特征是在Heap dump文件中不会看见明显的异常

内存分配和回收策略

  1. 对象优先在Eden分配

大多数状况状况下,对象在新生代Eden区中分配。虚拟机将发起一次Minor GC。

  • Minor GC :指发生在新生代的垃圾回收动做,由于java对象大多具有朝生夕灭的特性,因此Minor GC 很是频繁,通常回收速度很是快
  • 老年代GC(Major GC / Full GC):发生在老年代的GC,速度通常会比Minor GC慢10倍以上

2.大对象直接进入老年代

大对象指的是须要大量连续空间的java对象