JVM内存结构

  • 一、基本概念

  • 1.1)JVM内存模型

  • 实例化对象存放的位置

  一般情况下new出现的对象是存放在堆上;堆是由老年代、年轻代组成;年轻代分为Eden、From、To区域;From与To统称为:Survivor区;new出来的对象,一般来说放入Eden,但是也有可能放入老年代;

  • 堆内存空间分配原则

  堆的空间是有大小限制的,在JVM默认不调整的情况下老年代分配的空间站总共堆分配的2/3;Eden与Survivor区的比例为剩下的8:2;

  • GC执行逻辑

当Eden放满后会触发:minor gc,开始清理没有引用的对象(没有任何的引用的对象);
minor gc后存活的对象会放入From区域;
再次minor gc的时候会把有引用的对象放入To区域;
每经历一次minor gc,都会挪动一次,每经历一次minor gc存活对象都会在分代年龄里+1,当存活对象经历15次(可以调整)后会放入老年代;
当老年代方面后会触发“full gc”,会对整个堆及方法区进行回收,当full gc执行的时候会暂停应用程序的线程;

  • 1.2)线程模型

  • 线程栈

栈空间可以通过JVM参数“-Xss”来指定,默认是1M;注意一下,这里的值越大,相同内存的情况下JVM能够开放的线程越少;
通常情况下存放线程在运程中的局部变量都是存在栈内存上;
当Java虚拟机开启一个线程的时候,单独开辟的一个内存区域,所以栈内存区域是所在线程独享的;

  • 本地方法栈

古老的东西,现在一般么有人用了,例如:Thread().start()方法中就存在一个start0本地方法,使用native进行修饰的;是调用的C语言,所以本地方法栈也是当前线程私有的;

  • 方法区

在JDK1.8之前叫持久代;
又被成为元空间;属于类的一些信息——类元信息,包括字段、属性、符号、常量池、静态变量等;
变量名称、方法名称等都可以成为符号;
元空间:在JDK1.8使用的是直接内存,是使用的物理内存;

  • 栈帧

栈帧是一种数据结构,线程在执行方法的时候,java虚拟机会针对这个方法单独分配一块内存区域,这块内存区域就是当前方法自己对应的栈帧区域;

  • 程序计数器

也是一块内存,存放JVM线程马上要执行的下一行指令码内存地址或者行号或者正在执行指令码的行号;程序计数器也是每个线程私有的;

  • 操作数栈

存放操作数据零时的中转区域;

  • 方法出口

存放上一级方法调用的位置;

  • 二、理论实践

假设如下代码:

1 public class Test {
2     public static void main(String[] values){
3         int a = 1;
4         int b = 2;
5         int c = (a+b)*10;
6         System.out.println(c);
7     }
8 }

编译后查看对应的Class指令码文件,内容如下:

 1 Compiled from "Test.java"
 2 public class xuexi.Test {
 3   public xuexi.Test();
 4     Code:
 5        0: aload_0
 6        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 7        4: return
 8 
 9   public static void main(java.lang.String[]);
10     Code:
11        0: iconst_1
12        1: istore_1
13        2: iconst_2
14        3: istore_2
15        4: iload_1
16        5: iload_2
17        6: iadd
18        7: bipush        10
19        9: imul
20       10: istore_3
21       11: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
22       14: iload_3
23       15: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
24       18: return
25 }

  根据指令码可以得出如下结论:

  1)将常量1放入“操作数栈”;程序计数器=当前指令ID
  2)局部变量表中为a分配一个内存区域;程序计数器=当前指令ID
  3)将操作数栈中的常量1放入局部变量表中a所分配的内存区域中;程序计数器=当前指令ID
  4)第二行代码“int b = 2;”执行方式与上述三步相同;
  5)第三行代码开始
  6)将局部变量a内存中的数据放入操作数栈;
  7)将局部变量b内存中的数据放入操作数栈;
  8)执行int类型的加法(指令码:iadd),就是操作数栈中的值进行相加(1+2=3);
  9)将一个8位带符号的值压入操作数栈(指令码:bipush),这里也就是把10放入操作数栈;
  10)执行int类型的乘法(指令码:imul),将计算结果(3*10=30)放入操作数栈;
  11)局部变量表中为c分配一个内存区域;
  12)将操作数栈中的结果放入c所分配的内存中;

  这里要特别注意,指令码中iconst及istore是从1开始的,因为0是专门分配给this使用的;

 

posted @ 2019-10-07 10:32  xxsd  阅读(150)  评论(0)    收藏  举报