java虚拟机栈
我的理解:
学过这个数据结构就知道,它有FILO(First in Last out)的特点,很适合模拟运行一个函数或方法的过程,所以这就是运行函数的区域,同时也会保存一些局部的变量信息,引用信息等。
笔记:
首先它是线程私有的,也就是每个线程都有一个栈,它的生命周期和线程相同,它描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError。
- StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。
扩展:那么方法/函数如何调用?
Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。
Java 方法有两种返回方式:
- return 语句。
- 抛出异常。
不管哪种返回方式都会导致栈帧被弹出。
本地方法栈
基本和java虚拟机栈一样,只不过Java虚拟机运行的是java字节码,本地方法运行native标记的本地方法,基本一样,包括异常和返回。
在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
堆
java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
- 新生代(Eden,From Survivor,To Survivor)Eden 区、两个 Survivor 区都属于新生代(为了区分,这两个 Survivor 区域按照顺序被命名为 from 和 to)
- 老年代 (Old generation)
永久代(1.8后没有了)
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:
- OutOfMemoryError: GC Overhead Limit Exceeded : 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
- java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。(和本机物理内存无关,和你配置的对内存大小有关!)
方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
方法区也被称为永久代,关于方法区和永久代区别,参考这篇博客 java1.8,方法区(永久代)被移除,改成元空间(metaspace),或者说是换了一个实现,元空间使用的是直接内存,我理解为,永久代使用的是jvm内的内存,元空间使用的是jvm外部内存,元空间的大小是可以配置的,这里和永久代有一个最大的区别就是,如果不配置上限,元空间会占满所有物理内存,永久代则要受制于jvm的空间大小
当元空间溢出时会得到如下错误: java.lang.OutOfMemoryError: MetaSpace
运行时常量池
运行时常量池是方法区的一部分。 Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
emmmm很混乱,总之要了解常量池储存的是什么就好
- 字面量,如:字符串,基本数据类型,final修饰的常量值,等等
- 符号引用,可以理解为映射到对象信息地址的名字,包括类名,方法名,字段名
new过程发生了什么
首先要知道,new 产生的实例对象必然是保存在堆里面,不是方法区或者常量池,注意是实例对象
- 类加载检查,要在常量池里检查有没有这个符号引用,没有执行类加载
- 分配内存,这里好复杂,姑且大概知道有这个过程
- 初始化实例对象的值,比如一些基本数据类型初始化为0,所以这里可以知道我们定义的成员变量,即使我们没有初始化也可以使用
- 设置对象头,这个也很复杂…不太好说,就类似网络请求在头部会有数据的序号,校验和,本机地址,发送地址等信息,对象头就有引用信息,年代信息等等
- 执行init方法,这个不一定是指构造方法,但是必然包括构造方法,构造代码块,成员变量的赋值等等
堆和常量池太难理解了,从string类分析一下
String s = new String("abc");
这句代码生成了多少个对象?一开始看我还很懵,new 一个string 初始化又传入一个string,所以这里起码有两个对象,一个是双引号内的字符串常量,先常量池中查找是否有这个常量,如果没有就创建,那么这里生成一个,然后我们new了一个string实例,所以new产生的实例应该在堆里面,这个肯定要创建的,所以堆里面又创建了一个,最多会有两个,可能只创建一个。同时这里可以发现,new必定会创建对象,要千万注意!