JVM 启动运行Class文件时,会对JVM内存进行切分,我们可以将其分为线程共享区和线程独享区。如下图所示:
其运行时内存详细架构如下:
在JDK8中持久代(Permanent Generation)部分数据移到了元数据区(Metaspace),在JDK8中已经没有持久代。元空间的本质和持久代类似,都是对JVM规范中方法区的实现,不过元空间与持久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
1、堆内存heap
- 虚拟机启动时创建,被所有线程共享,用于存放所有java对象实例;
- 可分为年轻代(eden)和老年代(Tenured);
- 是垃圾收集器(GC)管理的主要区域;
- 堆中没有内存分配时将会抛出OutOfMemoryError;
- 非静态成员变量属于实例,因此会被分配在堆上,会线程共享,会有线程安全问题;
对象内存分配说明:
新创建的对象一般都分配在年轻代,当对象比较大时年轻代没有足够空间还可直接分配到老年代。有时候系统为了减少GC开销对于小对象且没有逃逸的对象还可以直接在栈上分配,分配到栈上不用GC,方法返回时就会被销毁。(对象逃逸简单来说就是在方法内创建的对象(局部变量),被作为返回参数传到外面去,被其他方法甚至线程使用,具体可分为全局逃逸和参数级逃逸,此时逃逸的对象是被分配到堆上的,需要被GC回收,实例代码如下:)
public class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
//此时sb对象是没有逃逸的,因为没有传到方法外面去,它就是方法内的局部变量,
//会被分配在方法栈上,方法结束返回时就会被销毁。
final StringBuilder sb = new StringBuilder()
.append("(")
.append(x)
.append(", ")
.append(y)
.append(")");
return sb.toString();
}
}
2、元数据区metaspace
- jvm在加载class时,创建instanceKlass,表示其元数据,包括常量池、字段、方法等,存放在方法区;instanceKlass是jvm中的数据结构;
- 在new一个对象时,jvm创建instanceOopDesc,来表示这个对象,存放在堆区,其引用,存放在栈区;它用来表示对象的实例信息;
- static修饰的静态成员变量(即类变量)放在方法区,也是线程共享区域,因此也是线程不安全的
3、内存配置参数
每个Java程序运行时都会使用一定量的内存, 而JVM内存不足以满足当前java程序运行时所需要的内存资源时就会出现内存溢出的现象。
这时要进行JVM内存调优,常用参数如下:
- -Xms:java Heap初始大小。 默认是物理内存的1/64。
- -Xmx:java heap最大值。建议均设为物理内存的一半。不可超过物理内存。
- -Xmn:young generation(年轻代)的heap大小。一般为Xmx的3、4分之一
- -XX:MetaspaceSize=128m 初始元空间大小,默认一般为21m。
- -XX:MaxMetaspaceSize=256m 最大元空间大小,默认无上限,由OS内存决定
- -XX:PermSize:设定内存的永久代初始大小
- -XX:MaxPermSize:设定内存永久保存区最大大小
- -XX:NewSize:设置新生代初始化大小
- -XX:MaxNewSize:设置新生代最大大小