JVM-内存模型
文章目录
前言
Java语言特点是与平台无关性。而Java虚拟机就是实现这一特点的关键。
比如下图是编译后产生文件是**.class文件是二进制的字节码**,字节码是不能直接被机器运行的,通过JVM把编译好的字节码转转换成不同操作系统可以直接识别的机器码指令。
一、JVM内存模型?
二、运行时数据区
2.1线程共享的
堆、方法区
(1)堆
- Head,java虚拟机管理内存区域最大的一块区域,java堆是所有线程共享的一块区域,此区域唯一目的就是存放实例对象,Dog dog = new Dog(),Dog dog是在栈中的实例,而new Dog()才是堆中的对象。几乎是所有实例对象以及数组都存在这里。
- java堆是垃圾回收器管理的主要区域,因此被称为GC堆。java堆还可以细分为:新生代和老年代:再细致一点就是:Eden、S0、S1、tentired。进一步划分就是为了更好的回收对象。大多数情况下,对象都会率先存在Eden区域分配,在一次新生代垃圾回收后,如果对象还活着则会进入S0或S1,并且对象的年龄会+1(Eden区->Survivor区后对象的初始年龄变为1),当默认年龄到达15之后,会被加到老年代。对象晋升的阈值可以通过参数:-XX:MaxTenuringThreshold来设置。
Java7中内存划分
年轻代、老年代、永久代
3. 年轻代:年轻代用来存放JVM刚分配的Java对象
4. 老年代:年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
5. 永久代:永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关。其大小上限收到虚拟机内存限制。
Java8中内存划分
JDK1.8之后改用元空间去实现方法区,而元空间跟永久代的最大区别就是元空间使用直接内存。
(2)方法区
线程的共享区域,用于存储已被虚拟机加载的类信息、常量、静态变量等。
- JDK1.7方法区也被称为永久代,实质上永久代只是方法区的实现方式,是方法区规范的实现。
- JDK1.8移除了永久代,取而代之的是元空间,元空间使用的是直接内存。与永久代很大不同的是,永久代如果不指定大小的话会随着类对象的创建,虚拟机会耗尽所有可用的内存。
当方法区内存大小无法满足内存分配时会抛出OutOfMemoryError异常
-XX:MetaspaceSize=N//设置MetaspaceSize大小
-XX:MaxMetaspaceSize=N//最大值
- 运行时常量池,运行时常量池是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息之外,还有常量池信息。JDK1.7之后已经将常量池从方法区中移除出来,在堆中开辟了一块区域存放运行时常量池。
(3)为什么要元空间取代永久代呢?
整个永久代有一个JVM本身设置固定值大小上限,无法进行处理。元空间使用的是直接内存,受本机可用内存的限制,并不会内存溢出。当然可以设置元空间大小在一个区间。这只是其中一个原因。
2.2线程私有
虚拟机栈、本地方法栈、程序计数器
(1)虚拟机栈
java虚拟机栈是由一个栈帧组成,它的生命周期和线程相同。每个方法在执行的时候会创建一个栈帧。当执行一个方法的时候也栈帧会被压入栈中,方法执行完成弹出栈。栈帧中都拥有:局部变量表、操作数栈、动态链表、方法出口信息等。java虚拟机栈是线程私有的。局部变量表中存放了编译期存放的各种数据类型(Boolean、byte、char、short、int、float、long、double);对象引用可能是一个指向对象起始地址的引用地址,也可能是指向代表对象句柄或者对象相关的位置。
在Java®虚拟机规范的第一版中:以下异常情况与 Java 虚拟机堆栈相关联:
- If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a . StackOverflowError
如果线程中的计算需要的 Java 虚拟机堆栈大于允许的堆栈,则 Java 虚拟机会抛出 .StackOverflowError
- If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an . OutOfMemoryError
如果可以动态扩展 Java 虚拟机堆栈,并尝试扩展,但没有足够的内存可用于实现扩展,或者如果没有足够的内存可用于为新线程创建初始 Java 虚拟机堆栈,则 Java 虚拟机将引发 .OutOfMemoryError
(2)本地方法栈
虚拟机栈和本地方法栈作用相似,区别在于虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为了虚拟机使用到的本地方法服务(native方法)。
(3)程序计数器
字节码解析器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、异常处理、线程恢复等功能都需要这个程序计数器完成。每条线程都需要有一个独立的程序计数器,互相之间不影响,独立存储。有两个作用:
- 字节码通过解析器改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到那里。
比如在下面代码中test1()中调用了test2(),test2()执行完成后退出,这时候需要回到test1()方法中继续执行,程序计数器记录了下一个需要执行的指令的行号。
public void test1(){
test2();
System.out.println("test1");
}
public void test2(){
System.out.println("test2");
}
总结
- 分清什么实例,什么是对象。Class a = new Class();此时a叫做实例,而不能说a是对象。实例在栈中,对象在堆中。操作实例通过只指针的间接操作对象。多个实例可以指向同一个对象。
- 栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中的对象不一定销毁。因为可能有其他变量也指向这个对象,直到栈中没有变量指向堆中的对象,他才销毁,而且还不是马上销毁,要等到垃圾回收器扫描时才可以被销毁。
- 类的成员变量在不同对象中各不相同,都有自己的存储空间(在堆的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。
- 生命周期:堆内存属于Java应用程序所使用,生命周期与jvm一致;栈内存属于线程所私有的,它的生命周期与线程相同。
- 引用:不论何时创建一个对象,他总是存储在堆内存空间,并且栈内存空间包含对他的引用,栈内存空间只包含方法原始数据类型局部变量以及堆空间中对象的引用变量。
- 在堆中对象可以全局访问,栈属于线程私有
- JVM栈内存管理比较简单,遵循LIFO原则,对空间内存管理较为复杂,分为新生代、老年代…