目录
1.JVM怎么分配空间?
指针碰撞:就是使用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点,等来了新的对象直接去空闲内存,并且移动一下指针即可。
操作比较简单
空闲列表:使用过内存与空闲内存交错,虚拟机维护一个列表,记录空闲内存,并且对新对象按需分配,需要多大空间取最适合的那一块,并且更新列表记录
需要一个列表
容易产生空间碎片问题
2.如何保证对象创建的安全性?
多线程情况下,正在给A分配内存,此时指针还没有来得及修改,对象B又进来了使用了原来的指针分配内存,有可能分配到刚刚占用的内存。
解决方案一:对分配空间的动作进行同步处理——虚拟机采用CAS加上失败重试的方式保证更新操作的原子性
解决方案二:把内存分配的工作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区,线程分配内存先分配给这个缓冲区,需要将会这个缓冲区先用完,然后在进行内存同步锁定。
3.对象的内存布局
对象在堆内存中的存储布局可以分为三个部分:对象头、实例数据和对其填充。
对象头:包括两类信息。并且在32位的虚拟中,它们各占32bit。
第一类用于存储对象自身的运行时数据, 如哈希码(HashCode) 、 GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等,被称为“Mark Word”,它被设计成一个有着动态定义的数据结构。
另一部分是类型指针(Klass Word),即对象指向它的类型元数据的指针。
如果是数组,还需要有一块用于记录数组长度的数据,也就是另外在加一组32bit存储空间。
Mark Word的32个比特存储空间中的25个比特用于存储对象哈希码, 4个比特用于存储对象分代年龄, 2个比特用于存储锁标志位, 1个比特固定为0,在其他状态(轻量级锁定、 重量级锁定、 GC标记、 可偏向) [1]下对象的存储内容如表
实例数据:对象真正储存的有效信息。
对其填充:对象的大小必须是8的整数倍
4.对象的访问定位
Java程序通过栈上的reference数据来操作堆上的具体对象。主流访问方式:使用句柄和直接指针。
使用句柄:划分一块内存作为句柄池,reference中存储的就是对象的句柄地址。好处:reference中存储的是稳定句柄地址。
直接指针:reference中存储的直接就是对象地址,如果只是访问自身的话,不需要多一次的间接访问开销。好处:速度快,节省了一次指针定位的开销。
5.内存溢出解决方案
1.Java堆溢出
Java堆是用来存储对象实例的,我们不断的创建对象,并且垃圾回收机制没有清除,总容量超过限制就会产生内存溢出异常。
public class HeapOOM {
static class OOMobject{
}
public static void main(String[] args) {
List<OOMobject> list = new ArrayList<OOMobject>();
while(true){
list.add(new OOMobject());
}
}
}
解决办法:
1.首先通过内存映像分析工具对Dump出来的堆转储快照进行分析。
2.第一步先确认内存中是出现了内存泄漏还是内存溢出。
3.如果是内存泄漏,进一步通过工具查看泄漏对象到GC Roots的引用链,找出泄漏对象是通过什么样的引用路径导致垃圾收集器无法回收他们,一般可以准确的定位到对象创建的位置,从而找到代码的具体位置。
4.如果不是内存泄漏,就证明这些对象是必须存活的,那就检查虚拟机的堆参数(-Xms)设置,看看是否有调整的空间,再看看有没有对象存活时间太长等不合理的情况,减少程序运行时的内存消耗。
2.虚拟机栈和本地方法栈溢出
1)如果线程请求的栈深度大于虚拟机所允许的最大深度, 将抛出StackOverflowError异常。
2)如果虚拟机的栈内存允许动态扩展, 当扩展栈容量无法申请到足够的内存时, 将抛出OutOfMemoryError异常。
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
解决方案:
出现StackOverflowError异常时, 会有明确错误堆栈可供分析。
如果是建立过多线程导致的内存溢出, 在不能减少线程数量或者更换64位虚拟机的情况下, 就只能通过减少最大堆和减少栈容量来换取更多的线程。 这种通过“减少内存”的手段来解决内存溢出的方式, 如果没有这方面处理经验, 一般比较难以想到,