Java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。这些区域都有各自的用途,以及建立和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而创建和销毁。根据《Java虚拟机规范(Java SE 7版)》的规定,Java虚拟机所管理的内存将会包括如下几个运行时数据区域,以下图所示:java
咱们能够将上面的数据区域分为线程独有、线程共享及其余三大区域:算法
Java是一门面向对象的语言,在Java程序运行的过程当中无时不刻都有对象被建立。在语言层面,建立对象一般是一个new关键字,可是,在虚拟机中,建立对象包括以下流程:shell
类加载 --> 分配内存 --> 内存空间初始化零值 --> 对象头设置 --> init初始化数组
分配内存的方式为:
“指针碰撞”:在内存规整状况下,将指针向空闲空间挪动一段与对象大小相等的距离。
“空闲列表”:在内存不规整状况下,虚拟机维护一个记录内存可用的列表,分配的时候从列表中找到一块空间划分给对象。
并发状况下的内存分配:
同步:对分配内存空间的动做进行同步处理———采用CAS配上失败重试的方式,保证更新操做的原子性
本地线程分配缓冲(TLAB):把内存分配动做按照线程划分在不一样空间中。即每一个线程在Java堆中预先分配一块内存TLAB,只有TLAB用完并从新分配新的TLAB时才须要同步。bash
在HotSpot虚拟机中,对象在内存中的存储布局能够分为3块区域:对象头(Header)、实例数据(Instance Data)和对其填充(Padding)数据结构
创建对象是为了使用对象。咱们的Java程序须要经过栈上的reference数据来操做堆上的具体对象。目前主流的访问方式有两种:句柄和直接指针。并发
句柄:Java堆中会划分出一块内存来做为句柄池,reference中存储的就是对象的句柄地址。句柄中包含了对象实例数据与类型数据各自的具体地址。布局
直接访问:reference指针存储的直接就是对象地址测试
使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(如垃圾收集时)时只会改变句柄中的实例数据指针,而reference自己不须要修改优化
直接访问最大的好处就是速度快。节省了一次指针定位的时间开销。HotSpot虚拟机使用第二种方式进行对象的访问。
-Xms 堆最小值 -Xmx 堆最大值 -XX:HeapDumpOnOutOfMemoryError能够在虚拟机出现异常时将堆存储快照
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
复制代码
public class HeadOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
复制代码
运行结果:
-Xss 设置栈的大小
-Xss228k
复制代码
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength ++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF stackSOF = new JavaVMStackSOF();
stackSOF.stackLeak();
}
}
复制代码
运行结果:
实验结果代表,在单线程下,当内存没法分配的时候,虚拟机抛出的都是StackOverflow异常
测试:建立线程致使内存溢出异常
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
new Thread(() -> {
dontStop();
}).start();
}
}
public static void main(String[] args) {
JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
javaVMStackOOM.stackLeakByThread();
}
}
复制代码
String.intern()方法返回的是常量池中的对象,若是池中没有对象,则建立对象返回引用
在JDK 1.6及以前的版本中,因为常量池分配在永久代内,咱们能够经过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量,测试代码:
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
复制代码