miracle just wanna be better

java内存结构

2019-11-01
miracle

java7内存结构

  • pc寄存器
  • java虚拟机栈
  • 本地方法栈
  • java堆
  • 方法区 其中pc寄存器,java虚拟机栈和本地方法栈线程隔离的一块内存区域.
    java堆和方法区是线程共享的一块区域

pc寄存器

内存里有几百个寄存器,每一个寄存器作用都不同,有一个寄存器是程序计数器,这个寄存器主要作用是存放下一条需要执行的指令

程序计数器的作用:处理器在一个时刻只能执行一个线程中的指令,但是程序是多线程的,为了线程切换后回到之前正确的位置上,就需要一个程序计数器来保存之前的状态
程序计数器指向下一行需要执行的字节码指令
再循环结构中,可以改变程序计数器中的值,来指向下一条需要执行的指令.在分支,循环,跳转,异常处理等场景都需要这个程序计数器 如果当前执行的是java的方法,则该寄存器中保存当前执行指令的地址,倘若执行的是native方法,则pc寄存器中为空.pc寄存器区域就是存放了n个这样的寄存器,此内存区域是唯一一个在java虚拟机规范中没有任何outOfMemoryError的区域

  1. 程序计数器指向下一条需要执行的指令
  2. 每一个线程都有一个程序计数器
  3. 执行java代码时,寄存器保存当前指定地址
  4. 执行native方法时,寄存器为空
  5. 不会产生OutOfMemoryError情况

java虚拟机栈

每一个线程都有自己的java虚拟机栈,这个栈与线程同时创建,一个线程中的每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程.每个线程有一个私有的栈,随着线程的创建而创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用),操作数栈,动态链接和返回地址等信息.当运行方法对应的栈帧叫做当前栈帧
局部变量表中存放了编译期间可知的各种基本数据类型(8种),对象引用,returnAddress类型(指向一条字节码指定的地址)

  • long和double类型占用2个局部变量空间(slot),其余数据占一个
  • 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的
  • 在方法运行期间不会改变局部变量表的大小

动态链接:
在线程中,一个方法调用另一个方法是通过符号的引用来实现的,动态连接的作用就是把这个符号引用表示的方法转化为实际方法的直接引用

java虚拟机栈可能发生的异常:

  • 如果线程请求分配的栈容量超过java虚拟机栈所允许的最大容量,java虚拟机就会抛出StackOverflowError
  • 如果java虚拟机栈动态扩展,在扩展没有申请到足够的内存或者是创建新线程时没有足够的内存再创建java虚拟机栈了,就会抛出outOfMemoryError

本地方法栈

与虚拟机栈类似,区别是虚拟机栈执行java方法,本地方法栈执行native方法,在虚拟机规范中对本地方法栈使用的语言,使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它

java堆

java堆是所有线程共享的一块内存区域,在虚拟机启动时创建,用来存放对象实例,是内存中最大的一块内存空间.GC在该区域回收不使用的对象内存空间,但不是所有的对象都在这保存,随着JIT编译器的发展和逃逸分析计数主键成熟,栈上分配,标量调换优化技术将会导致一些微妙的变化,所有的对象都分配在堆上也逐渐变得不那么绝对.
堆的大小可以固定,也可以动态扩展,可以通过-Xms(最小值)和-Xmx(最大值)参数设置,如果在堆中没有内存完成实例分配,且堆无法扩展时,会抛出outOfMemoryError异常
为了支持垃圾收集,常被分成三部分

  • 年轻代:
    1. Eden区 8:
    2. survivor区 1:1
  • 永久代:(jdk8已经移除,元空间代替了它)

方法区

方法区也是所有线程共享.主要用于存储类的信息,常量池,静态变量,及时编译器编译后的代码等数据.方法区逻辑上属于堆的一部分.通常又叫Non-heap(非堆)

举例

public class Person {
	int age;
	String name;
	public void walk(){
		System.out.println("走路");
	}
}

public class Test {
	public static void main(String[] args) {
		Person person=new Person();
		person.name="呵呵";
		person.age=18;
		person.walk();
	}
}
  1. jvm去方法区寻找Test类的代码信息,如果有直接调用,如果没有就用类的加载机制把类加载进来,同时把静态变量,静态方法,常量加载进来
  2. jvm进入main方法,看到Person person=new Person();首先分析Person类,寻找Person类的代码信息,有就加载,没有就用类加载机制加载进来,同时加载静态变量,静态方法,常量(“走路”),
  3. jvm看到person,person在main方法内部,所以是局部变量,放在栈中
  4. jvm看到new Person,new出来的对象放在堆中
  5. jvm看到=,把new Person的地址告诉person变量,person通过四字节的地址,引用该实例
  6. jvm看到person.name=”呵呵”,person通过引用new Person实例的name属性,该name属性通过地址指向常量池的”呵呵”.
  7. jvm看到person.age=18,person的age属性是基本数据类型,直接赋值
  8. jvm看到person.walk(),调用实例的方法时,并不会在实例对象中生成一个新的方法,而是通过地址指向方法区中的类信息的方法.

java8内存结构

  • 永久代(PermGen): 就是堆区
    只有HotSpot才有PermGen space
  • 元空间(metaspace) 元空间和永久代类似,都是jvm规范中方法区的实.不过两者最大的区别在于,元空间并不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间的大小仅受本地内存限制

运行时常量池

java6时,他是方法区的一部分,java7又把它放在了堆内存中,1.8后出现了元空间,又回到了方法区

运行时常量池在JDK1.6及之前版本的JVM中是方法区的一部分,而在HotSpot虚拟机中方法区放在了”永久代(Permanent Generation)”。所以运行时常量池也是在永久代的。 
但是JDK1.7及之后版本的JVM已经将字符串常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。

String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。


Comments

Content