1、程序计数器:线程私有,如果线程正在执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址(个人理解认为在GC时可以确定要回收哪些内存资源)。如果线程正在执行native方法(非java实现的方法,比如C/C++提供了一个接口。但是native方法默认是有具体的实现类,所以不可与抽象类同用),这个计数器值为空(undefined),此内存是是唯一一个在java虚拟机规范中没有规定任何outOfMemoryError情况的区域。
2、java虚拟机栈:线程私有,为虚拟机执行Java方法(字节码服务),如果线程请求的栈深度(单线程的栈深度只的是当前线程存放的变量,对象,中间运行结果等,栈帧的数量)大于虚拟机所允许的深度,将抛出StackOverflowError异常,(可以通过-Xss加大内存)如果虚拟机可以动态扩展,如果扩展时无法申请足够的内存,就会抛出OutOfMEmoryError异常。
3、本地方法栈:线程私有,为虚拟机使用Native方法服务。
4、java堆:线程共享,Java虚拟机管理的内存中最大的一块,仅用于存放对象实例,java堆是垃圾收集器管理的主要区域。Java堆可以处于物理上不连续的内存空间中,只要逻辑是连续的即可。java堆既可以实现成固定大小的,也是可以扩展的,通过X-Xmx和Xms控制。
5、方法区:线程共享,用于存储被虚拟机加载的类信息、常量、静态变量。即编译器编译后的代码等数据。当方法区无法申请到内存。(比如前面执行的代码占满了内存,后面执行的方法在执行被初次调用生成对象之后,就会出现内存溢出)。
6、运行常量池:运行常量池是方法区的一部分,常量池用于存放编译期生成的各种字面量和符号引用,一般来说,还会把翻译出来的直接引用也存储在运行时常量池,当常量池无法申请到内存时会抛出OutOfMemoryError异常。
7、直接内存:不属于虚拟机运行时数据区的一部分,内存直接分配不受Java堆大小的限制,但是受本机总内存大小和处理器寻址空间的限制,动态扩展时可能出现OutOfMemoryError异常。
堆和栈的区别
1、栈内存用来存储局部变量和方法调用。而堆内存用来存储java中的对象。
2、栈内存属于线程内存,即每个线程都有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存的对象是所有线程可见,可以被所有线程访问。
3、如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。
4、栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。
什么情况下会发生栈内存溢出?
- 是否有递归调用
- 是否有大量循环或死循环
- 全局变量是否过多
- 数组、List、map数据是否过大
- 使用DDMS工具进行查找大概出现栈溢出的位置
什么情况下会发生堆内存溢出?
是否App中的类中和引用变量过多使用了Static修饰
是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象)
是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)
检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过一定数量,就可能会造成内存溢出。所以在查询时应采用“分页查询”。
检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。
检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。