JVM 启动运行Class文件时,会对JVM内存进行切分,我们可以将其分为线程共享区和线程独享区。如下图所示:

Java的内存结构 元空间 静态变量 jvm内存模型元空间_java


Java的内存结构 元空间 静态变量 jvm内存模型元空间_Java的内存结构 元空间 静态变量_02


其运行时内存详细架构如下:

Java的内存结构 元空间 静态变量 jvm内存模型元空间_堆栈_03


在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程序运行时所需要的内存资源时就会出现内存溢出的现象。

Java的内存结构 元空间 静态变量 jvm内存模型元空间_堆栈_04


这时要进行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:设置新生代最大大小

Java的内存结构 元空间 静态变量 jvm内存模型元空间_jvm_05