java内存区域
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的:
- 堆
- 方法区
- 直接内存
程序计数器:记录当前线程执行的位置 当线程切换后能够知道该线程上次运行到哪了
java虚拟机栈:
方法调用的数据通过栈进行传递,每次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,会有一个栈帧被弹出
栈由一个个栈帧组成,每个栈帧包括:局部变量表、操作数栈、动态连接、方法返回地址
局部变量表:主要存放了编译器可知的各种数据类型、对象引用
操作数栈:主要作为方法调用中的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中
动态连接:服务于一个方法需要调用其它方法的场景。用于将符号引用转换为调用方法的直接引用。
java方法有两种返回方式,一种是return,一种是抛出异常,不论怎么返回,栈帧都会弹出。即栈帧随着方法调用而创建,随着方法结束而销毁。
OutOfMemoryError:HotSpot虚拟机的栈容量并不能动态扩展,线程申请栈空间只要成功了就不会OOM,但如果申请失败会抛出OOM
本地方法栈:虚拟机栈为虚拟机执行java方法服务,本地方法栈则为虚拟机使用到的Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。
堆
堆是java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。
几乎所有的对象实例以及数组都在这里分配内存
java堆时垃圾收集器管理的主要区域,因此也被称作GC堆。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以java堆还可以细分为:新生代和老年代
在jdk7及以前堆内存分为 新生代 老生代 永久代
在jdk8之后 永久代被元空间取代
大部分情况 对象首先在Eden区域分配,再一次新生代垃圾回收后,如果对象还存活,则会进入S0或S1,并且对象的年龄还会加1(Eden到Survivor区后对象的初始年龄变为1)
当它的年龄增加到一定程度(默认为15),就会被晋升到老年代
堆这里最容易出现的就是OutOfMemory错误
1、java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
JVM花大量时间执行垃圾回收并且只能回收很少的堆空间时
2、java.lang.OutOfMemoryError: Java heap space
创建新的对象时,堆内存中的空间不足以存放新创建的对象
方法区
方法区属于时JVM运行时数据区域的一块逻辑区域
设置元空间:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
运行时常量池
将编译后的类信息放入方法区中,用来动态获取类信息:class文件元信息描述、编译后的代码数据、引用类型数据、类文件常量池等
字符串常量池
为字符串专门开辟的一块区域,用于避免字符串的重复创建
字符串常量池为什么在堆里? – 方法区实现的gc回收效率太低,但字符串gc回收的频率非常高
直接内存
直接内存是一种特殊的内存缓冲区,并不在java堆或方法区中分配的。而是在本地内存中。
虚拟机对象创建过程
1、类加载检查
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过,没有的话先执行相应的类加载过程
2、内存分配
为新生对象分配内存,对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来
3、初始化零值
将分配到的内存空间都初始化为零值
4、设置对象头
对对象进行必要的设置,如对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的gc分代年龄等信息。
5、执行init方法
对java来说,此时对象创建才刚刚开始,还需要执行init方法