梦想还是要有的,万一忘了咋办?

0%

Java内存结构

Java内存结构图

upload successful

程序计数器

介绍

程序计数器(Program Counter Register)是一块很小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器(仅是在概念模型,各种虚拟机有可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器。

特征

  • 线程隔离,线程间互不影响。
  • 唯一没有规定OutOfMemoryError内存区域。

Java虚拟机栈

介绍

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的 内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈到出栈的过程。

编译程序代码的时候,就已经确定了局部变量表和操作数栈的大小,而且在方法表的Code属性中写好了。不会受到运行期数据的影响。

upload successful

局部变量表

是一片逻辑连续的内存空间,最小单位是Slot,用来存放方法参数和方法内部定义的局部变量。

Slot

虚拟机没有明确指明一个Slot的内存空间大小。但是boolean、byte、char、short、int、float、reference、returnAddress类型的数据都可以用32位空间或更小的内存来存放。这些类型占用一个Slot。Java中的long和double类型是64位,占用两个Slot。(只有double和long是jvms里明确规定的64位数据类型)

虚拟机如何调用

1
2
3
4
table = Solat[];
table[0] = this;
table[1……n-1];//对应参数1,参数2…… 参数n

Solt复用对GC的影响

idea 启动gc调试
upload successful
测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.hardydou.jmm;

import org.junit.Test;

public class SoltGcTest {
/**
* 内存不会回收
* [Full GC (System.gc()) 66856K->66732K(98304K), 0.0117163 secs]
*/
@Test
public void scenes1() {
byte[] bs = placeHolder(64);
System.gc();
}
/**
* bs 作用域结束,内存还不回收
* [Full GC (System.gc()) 66373K->66371K(98304K), 0.0135849 secs]
*/
@Test
public void scenes2() {
{
byte[] bs = placeHolder(64);
}
System.gc();

}
/**
* Solt 重用,内存回收
* [Full GC (System.gc()) 66560K->835K(98304K), 0.0067793 secs]
*/
@Test
public void scenes3() {
{
byte[] bs = placeHolder(64);
}
int a = 0;
System.gc();
}
/**
* 及时回收
* <p>
* [Full GC (System.gc()) 66435K->835K(98304K), 0.0108251 secs]
*/
@Test
public void scenes4() {
{
byte[] bs = placeHolder(64);
bs = null;
}
System.gc();
}
public static byte[] placeHolder(int n) {
System.out.println("SoltGcTest.placeHolder allocation mem : " + n + "m");
byte[] placeholder = new byte[n * 1024 * 1024];
System.out.println("SoltGcTest.placeHolder allocation Success ");
return placeholder;
}
}

操作栈

操作栈又称为操作数栈,是后进先出栈。编译时确定

MAXSTACK = 2

字节码指令

局部变量必须赋值

不同于类变量存在准备阶段。类变量有两次赋初始值的过程:

  1. 准备阶段,赋予系统初始值;
  2. 初始化阶段,赋予程序员定义的初始值;

因此类变量及时在初始化阶段程序员未赋值,也不会影响使用。但是局部变量没有赋值是不可以使用的。
upload successful

栈帧重叠

在概念模型中,两个栈帧作为虚拟机的元素,是完全相互独立的。但是大多虚拟机的实现里都会做一些优化处理,另两个栈帧出现重叠区域。方便共享数据避免额外数据复制传递。
upload successful

动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态链接(Dynamic Linking)。

静态解析:类加载阶段或者第一次使用的时候就转化为直接引用。  
动态链接:每一次运行期间转化为直接引用。

方法调用

解析

编译期间就完全确定,在类装载解析阶段就会把涉及到的符号引用转换为可以确定的直接引用。

字节码指令
  • invokestatic 调用的方法
  • invokespecial 调用的方法
  • invokevirtual 调用 被 final 修饰的 方法
静态解析示例
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void say() {
System.out.println(" Hello word !");
}
/**
* 方法调用-解析-静态解析
* <p>
* INVOKESTATIC com/hardydou/jmm/DynamicLink.say ()V
*/
@Test
public void scene3() {
DynamicLink.say();
}

分派

面向对象基本特征:继承、封装、多态。分派会解释多态特征,如”重载“、”重写“在虚拟机中是如何实现的。
虚拟机是如何找到正确的目标方法的。

字节码指令
  • invokevirtual
静态分派示例-重载

编译期间就完全确定,在类装载解析阶段就会把涉及到的符号引用转换为可以确定的直接引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static abstract class Human {}
static class Man extends Human {}
static class Woman extends Human {}

public void say(Human human) {
System.out.println("Hello guy !");
}

public void say(Man man) {
System.out.println("Hello man !");

}

public void say(Woman woman) {
System.out.println("Hello woman !");
}

/**
* 方法调用-分派-静态分派示例1
* 代码编译期间就完全确定,
* 在类装载的解析阶段就会把设计的符号引用全部转变为可确定的直接引用
* 父类型引用子类型
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Human;)V
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Human;)V
*/
@Test
public void scene1() {
DynamicLink dc = new DynamicLink();
DynamicLink.Human man = new DynamicLink.Man();
dc.say(man);
man = new DynamicLink.Woman();
dc.say(man);

}
/**
* 方法调用-分派-静态分派示例2
* 子类型引用
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Man;)V
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink.say (Lcom/hardydou/jmm/DynamicLink$Woman;)V
*/
@Test
public void scene2() {
DynamicLink dc = new DynamicLink();
DynamicLink.Man man = new DynamicLink.Man();
dc.say(man);
DynamicLink.Woman woman = new DynamicLink.Woman();
dc.say(woman);
}
动态分派示例-重写

运行结果与字节码不一定相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static abstract class Human {
abstract void say();
}

static class Man extends Human {

@Override
void say() {
System.out.println("Hello man");
}
}

static class Woman extends Human {
@Override
void say() {
System.out.println("Hello woman");
}
}
/**
* 方法调用-分派-动态分派
* <p>
* 实际执行结果与 字节码 不相同
* <p>
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink$Human.say ()V
* INVOKEVIRTUAL com/hardydou/jmm/DynamicLink$Human.say ()V
*/
@Test
public void scene5() {
DynamicLink.Human human = new DynamicLink.Man();
DynamicLink.Human woman = new DynamicLink.Woman();
Object obj = new Object();
human.say();
human = new DynamicLink.Woman();
human.say();
}
小结

截止java1.8,java是静态多分派,动态单分派语言。

方法返回地址

当一个方法开始执行后,有两中方式退出:正常完成出口、异常完成出口。无论哪种方法退出,退出后都要返回到方法被调用到的位置,程序才能继续执行,方法返回时需要栈帧中保持一些信息,用来帮助恢复它的上层方法执行状态。

正常完成出口(Normal Method Invocation Completion)

执行引擎遇到任意一个方法返回指令码

 return 

异常完成出口(Abrupt Method Invocation Completion)

方法执行过程中遇到异常,并且这个异常没有在方法体内得到处理。
无返回值

Java堆

介绍

所有线程共享最大内存空间,虚拟机启动时创建。虚拟机规范描述:所有对象实例、数组都要在堆上分配,但随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化。是垃圾收集器管理的主要区域。不同的垃圾回收算法也决定了堆内存细分方式。

  • 逻辑连续即可,不需要物理连续;
  • -Xmx 最大内存
  • -Xms 最小内存

特征

  • 允许抛出 OutOfMemoryError
  • 线程共享
  • 最大一块内存,对象实例、数组 都在这分配空间。
  • GC算法直接决定细分空间;

方法区

介绍

所有线程共享内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却有一个别名:Non-Heap(非堆)目的是与Java堆分开来。

特征

  • 会抛出OutOfMemoryError
  • 线程共享

运行时常量池

运行时常量池(Run Constant Pool)是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

直接内存

介绍

直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,而且也可能会导致OutOfMemoryError异常出现。

存在方式

JDK1.4引入的NIO,Channel、Buffer可以通过Native直接分配堆外内存,然后通过Java堆中的DirectByteBuffer引用。避免Java堆与Native堆中来回复制数据,提升效能明显。

特征

  • 非Java虚拟机内存区域
  • 允许抛出 OutOfMemoryError(物理内存不够用时)
  • Jdk1.4 Nio 引入
  • 不受 -Xmx 影响