JVM

【深入理解Java虚拟机】【02】java 内存

Posted by Charlie on 2019-05-26

[TOC]

1. 运行时数据区域

img

1.1.程序计数器

Q:如果是新启动的一个线程,那么不会因为线程异步问题,无法控制执行顺序吗?

A:当前线程应当会被阻塞,直到另外一个线程执行结束。例如:通过死循环来控制阻塞(当然死循环效率太低,这里只是一个例子)

1.2.虚拟机栈

1.3.栈帧

img

一个线程中,当线程调用某个方法时,JVM会相应的创建一个栈帧(Stack Frame),放入虚拟机栈中,用来表示某个方法的调用。

栈帧是用来存储数据,和存储部分过程结果的数据结构,同时也用来处理动态链接(Dynamic linking)、方法返回值、异常分派(Dispatch Exception)。

线程对(某个对象的)方法的调用,就对应着一个栈帧的入栈、出栈(虚拟机栈)的过程。线程在运行过程中,只有一个栈帧是处于活跃状态,称为当前活动栈帧,当前活动栈帧,始终处于虚拟机栈的栈顶,当前活动栈帧对应的方法,也是当前线程正在执行的方法。

1.4.本地方法栈

本地方法栈,为虚拟机使用到的 native 方法服务。在虚拟机规范中对本地方法栈中方法是用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpot 虚拟机)直接就把本地方发展和虚拟机栈合二为一。

1.5.Java 堆

1.6.方法区

从 JDK1.8 开始,已经移除方法区

img

1.7.运行时常量池

1.8.直接内存

直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。

2.虚拟机中的对象

2.1.对象创建过程

  1. 虚拟机遇到一条 new 指令

  2. 检查 new 指令参数在常量池中是否能够定位到一个类的符号引用

    符号引用?

  3. 检查这个符号引用代表的类,是否已经被加载、解析、初始化过,若没有,则必须先执行响应的类加载过程

  4. 类加载检查通过后,虚拟机为新生对象分配内存。

    一个类所需要的内存大小,在类加载通过后就可以完全确定

  5. 内存分配完成后,需要将分配到的内存空间初始化为零值(不包括对象头)

    例如类中的基础类型属性,设置为其对应的初始值,引用类型属性,设置为 null

  6. 对对象做必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC分代年龄等信息,这些信息,存放到对象的对象头(Object Header)中。

    对象的分代年龄,保存在哪里?

  7. 以上是虚拟机对对象所进行的操作,下面是执行方法,进行初始化

      方法,是否就是执行构造方法?

2.2.对象内存布局

在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头(Object Header)、实例数据(Instance Data)、对齐填充(Padding)

image-20190819164820977

image-20190819170927946

2.2.1.对象头

对象头记录对象的信息,包括哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向时间戳,类型指针

HotSpot虚拟机的对象头包括两部分信息。

2.2.2. 32位虚拟机Mark Word

1
2
3
4
5
6
7
8
9
10
11
12
13
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|

2.2.3. 64位虚拟机Mark Word

1
2
3
4
5
6
7
8
9
10
11
12
13
|------------------------------------------------------------------------------|--------------|
| Mark Word (64 bits) | State |
|------------------------------------------------------------------------------|--------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal |
|------------------------------------------------------------------------------|--------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased |
|------------------------------------------------------------------------------|--------------|
| ptr_to_lock_record:62 | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------|
| | lock:2 |Marked for GC |
|------------------------------------------------------------------------------|--------------|

2.2.4. Mark Word各部分含义

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向管程Monitor的指针。

lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:

img

2.2.2.实例数据

对象真正存储的有效信息,即程序代码中定义的各种类型的字段内容

实例数据存储的是什么内容?是否是 Class 加载后的信息?

2.2.3.对齐填充

HotSpot JVM 要求对象的大小是 8 字节的整数倍,如果数据没有对齐,则需要对齐填充来补全

补全的策略是什么

2.3.对象访问定位

目前 HotSpot 虚拟机是使用直接指针的方式实现的

3.虚拟机栈与其他内存的关系

注意:-Xss 设置的是单个线程的栈大小。

进程内存减去 Xmx (若 java8 以下的虚拟机还需要减去 MaxPermSize),程序计数器消耗内存很小可以忽略

剩下的内存由虚拟机栈和本地方法栈瓜分。

所以每个线程分配到的栈容量越大,可以建立的线程数量就越小。

参考资料

https://blog.csdn.net/luanlouis/article/details/40043991