一、 java虚拟机内存区域主要有:方法区、堆、虚拟机栈、本地方方法栈、程序计数器
按照线程私有和共有来分:线程私有的有--程序计数器,虚拟机栈,本地方法栈。共有的有--本地方法区,堆
1、程序计数器:主要功能是控制程序字节码的指令,分支、循环、跳转、异常处理、线程恢复等功能
2、java虚拟机栈(stack):存放局部变量(8中基本数据类型、对象引用、地址引用)、操作数栈、动态链接、方法出口等
3、本地方法栈:与java虚拟机类似,java虚拟机为字节码服务,而他是为本地方法服务,Sun HotSport虚拟机把这两者合二为一了
4、java堆(Heap):主要存放对象的实例和数组
5、方法区:主要存放已被虚拟机加载的类信息(Class)、常量、静态变量、编译器编译后的代码
6、运行时常量池(方法区的一部分):存放编译器生成的各种字面量和符号引用
7、直接内存:不是运行时内存的一部分,特殊场景也会使用
二、java对象分配内存
最常见的创建对象的方法是new,当使用new关键字创建对象时,虚拟机首先会检查这个指令的参数是否在常量池中定位到一个类的符号引用,检查这个符号引用代表的类是否已经被加载、解析和初始化。
检查听过后给对象分配内存,分配的方式有两种:
1、假设堆中的对象都是排列紧密的,内存放在指针的一边,另一边是空闲的,只要移动下指针,继续存放下一个对象就可以了,这叫做“指针碰撞”
2、当然更可能的时候对象时杂乱的,虚拟机就维护一个列表,记录哪块内存区域是可用的,分配的时候找一块足够大的空间内存给对象,叫做"空闲列表"
可见堆内存有可能很整洁也有可能很杂乱,这是由垃圾收集器是否有压缩整理功能决定的。Serial、ParNew等带有Compact过程的收集器使用分配算法是指针碰撞,而CMS这种基于Mark-Sweep算法的收集器,通常采用空闲列表。
三、最常见的异常
最常见的异常是OutOfMemoryError内存溢出异常。
通常配置方法是:-Xms20m -Xmx 100m
虚拟机栈和本地方法栈同时也有另外一种异常StackOverflowError,两者区别:
1、线程请求的栈深度大于虚拟机所允许的深度,StackOverflowError
2、虚拟机在扩展时超过所能允许申请的内存,OutOfMemoryError
但是这两种异常有类似重复的地方:当线程获取不到足够的内存时,到底是内存太小还是线程用的栈太大了?
看如下情况:
1、-Xss改小栈内存容量,抛出StackOverflowError异常,且输出的堆栈深度相应减小
2、定义了大量的本地变量,增大本地方法栈本地变量表的长度,还是抛出StackOverflowError异常,且输出的堆栈深度相应减小
实际的原因是:在单线程的情况下,无论是栈帧太大还是内存太小都会抛出StackOverflowError异常
而多线程的情况下,如果创建的线程太多则会导致内存溢出错误,即OutOfMemoryError,为什么呢?
操作系统分配的每个进程的内存是有限的,如32位的windows限制为2GB,除去堆和方法区,剩下的程序计数器忽略不计,不算虚拟机自身消耗,剩下的都被虚拟机栈和本地方法栈消耗了,每个线程分配到的栈容量越大,可以建立的线程数就越少,不停的建立线程时就容易把剩下的呢村耗尽,就会抛出内存溢出。
改进方法:
1、减少线程数
2、换64位虚拟机
3、减少堆内存和本地方法内存容量换取更多的线程