目录重构

This commit is contained in:
youthlql
2021-12-06 22:31:03 +08:00
parent e40677d718
commit 4282b0e7ce
44 changed files with 37 additions and 43 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,408 @@
---
title: JVM系列-第3章-运行时数据区
tags:
- JVM
- 虚拟机
categories:
- JVM
- 1.内存与垃圾回收篇
keywords: JVM虚拟机。
description: JVM系列-第3章-运行时数据区。
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/logo.png'
abbrlink: a7ad3cab
date: 2020-11-09 15:38:42
---
> 此章把运行时数据区里比较少的地方讲一下。虚拟机栈,堆,方法区这些地方后续再讲。
运行时数据区概述及线程
=================
前言
----
本节主要讲的是运行时数据区,也就是下图这部分,它是在类加载完成后的阶段
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0001.png">
当我们通过前面的:类的加载 --> 验证 --> 准备 --> 解析 --\> 初始化,这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0002.png">
类比一下也就是大厨做饭,我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0003.png">
运行时数据区结构
----------
### 运行时数据区与内存
1. 内存是非常重要的系统资源是硬盘和CPU的中间仓库及桥梁承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略保证了JVM的高效稳定运行。**不同的JVM对于内存的划分方式和管理机制存在着部分差异**。结合JVM虚拟机规范来探讨一下经典的JVM内存布局。
2. 我们通过磁盘或者网络IO得到的数据都需要先加载到内存中然后CPU从内存中获取数据进行读取也就是说内存充当了CPU和磁盘之间的桥梁
> 下图来自阿里巴巴手册JDK8
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0004.jpg">
### 线程的内存空间
1. Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区其中有一些会随着虚拟机启动而创建随着虚拟机退出而销毁。另外一些则是与线程一一对应的这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
2. 灰色的为单独线程私有的,红色的为多个线程共享的。即:
- 线程独有:独立包括程序计数器、栈、本地方法栈
- 线程间共享:堆、堆外内存(永久代或元空间、代码缓存)
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0005.png">
### Runtime类
**每个JVM只有一个Runtime实例**。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0006.png">
线程
----
### JVM 线程
1. 线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行
2. **在Hotspot JVM里每个线程都与操作系统的本地线程直接映射**
- 当一个Java线程准备好执行以后此时一个操作系统的本地线程也同时创建。Java线程执行终止后本地线程也会回收
4. 操作系统负责将线程安排调度到任何一个可用的CPU上。一旦本地线程初始化成功它就会调用Java线程中的run()方法
> 关于线程并发可以看笔者的Java并发系列
### JVM 系统线程
- 如果你使用jconsole或者是任何一个调试工具都能看到在后台有许多线程在运行。这些后台线程不包括调用`public static void main(String[])`的main线程以及所有这个main线程自己创建的线程。
- 这些主要的后台系统线程在Hotspot JVM里主要是以下几个
1. **虚拟机线程**这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点这样堆才不会变化。这种线程的执行类型括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销
2. **周期任务线程**:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行
3. **GC线程**这种线程对在JVM里不同种类的垃圾收集行为提供了支持
4. **编译线程**:这种线程在运行时会将字节码编译成到本地代码
5. **信号调度线程**这种线程接收信号并发送给JVM在它内部通过调用适当的方法进行处理
程序计数器(PC寄存器)
===========
PC寄存器介绍
----------
> 官方文档网址https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0007.png">
1. JVM中的程序计数寄存器Program Counter RegisterRegister的命名源于CPU的寄存器**寄存器存储指令相关的现场信息**。CPU只有把数据装载到寄存器才能够运行。
2. 这里并非是广义上所指的物理寄存器或许将其翻译为PC计数器或指令计数器会更加贴切也称为程序钩子并且也不容易引起一些不必要的误会。**JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟**。
3. 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
4. 在JVM规范中每个线程都有它自己的程序计数器是线程私有的生命周期与线程的生命周期保持一致。
5. 任何时间一个线程都只有一个方法在执行,也就是所谓的**当前方法**。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址或者如果是在执行native方法则是未指定值undefned
6. 它是**程序控制流**的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
7. 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
8. 它是**唯一一个**在Java虚拟机规范中没有规定任何OutofMemoryError情况的区域。
## PC寄存器的作用
PC寄存器用来存储指向下一条指令的地址也即将要执行的指令代码。由执行引擎读取下一条指令并执行该指令。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0008.png">
举例
------
```java
public class PCRegisterTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
String s = "abc";
System.out.println(i);
System.out.println(k);
}
}
```
查看字节码
> 看字节码的方法https://blog.csdn.net/21aspnet/article/details/88351875
```java
Classfile /F:/IDEAWorkSpaceSourceCode/JVMDemo/out/production/chapter04/com/atguigu/java/PCRegisterTest.class
Last modified 2020-11-2; size 675 bytes
MD5 checksum 53b3ef104479ec9e9b7ce5319e5881d3
Compiled from "PCRegisterTest.java"
public class com.atguigu.java.PCRegisterTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#26 // java/lang/Object."<init>":()V
#2 = String #27 // abc
#3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #30.#31 // java/io/PrintStream.println:(I)V
#5 = Class #32 // com/atguigu/java/PCRegisterTest
#6 = Class #33 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/atguigu/java/PCRegisterTest;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 i
#19 = Utf8 I
#20 = Utf8 j
#21 = Utf8 k
#22 = Utf8 s
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 SourceFile
#25 = Utf8 PCRegisterTest.java
#26 = NameAndType #7:#8 // "<init>":()V
#27 = Utf8 abc
#28 = Class #34 // java/lang/System
#29 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#30 = Class #37 // java/io/PrintStream
#31 = NameAndType #38:#39 // println:(I)V
#32 = Utf8 com/atguigu/java/PCRegisterTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (I)V
{
public com.atguigu.java.PCRegisterTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/atguigu/java/PCRegisterTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: ldc #2 // String abc
12: astore 4
14: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
17: iload_1
18: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
21: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_3
25: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
28: return
LineNumberTable:
line 10: 0
line 11: 3
line 12: 6
line 14: 10
line 15: 14
line 16: 21
line 18: 28
LocalVariableTable:
Start Length Slot Name Signature
0 29 0 args [Ljava/lang/String;
3 26 1 i I
6 23 2 j I
10 19 3 k I
14 15 4 s Ljava/lang/String;
}
SourceFile: "PCRegisterTest.java"
```
* 左边的数字代表**指令地址(指令偏移)**,即 PC 寄存器中可能存储的值,然后执行引擎读取 PC 寄存器中的值,并执行该指令
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0009.png">
两个面试题
-------
**使用PC寄存器存储字节码指令地址有什么用呢**或者问**为什么使用 PC 寄存器来记录当前线程的执行地址呢?**
1. 因为CPU需要不停的切换各个线程这时候切换回来以后就得知道接着从哪开始继续执行
2. JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0010.png">
**PC寄存器为什么被设定为私有的**
1. 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法CPU会不停地做任务切换这样必然导致经常中断或恢复如何保证分毫无差呢**为了能够准确地记录各个线程正在执行的当前字节码指令地址最好的办法自然是为每一个线程都分配一个PC寄存器**,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
2. 由于CPU时间片轮限制众多线程在并发执行过程中任何一个确定的时刻一个处理器或者多核处理器中的一个内核只会执行某个线程中的一条指令。
3. 这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。
> 注意并行和并发的区别,笔者的并发系列有讲
CPU 时间片
---------
1. CPU时间片即CPU分配给各个程序的时间每个线程被分配一个时间段称作它的时间片。
2. 在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。
3. 但在微观上由于只有一个CPU一次只能处理程序要求的一部分如何处理公平一种方法就是引入时间片**每个程序轮流执行**。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0011.png">
# 本地方法接口
## 本地方法
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0012.png">
1. 简单地讲,**一个Native Method是一个Java调用非Java代码的接囗**一个Native Method是这样一个Java方法该方法的实现由非Java语言实现比如C。这个特征并非Java所特有很多其它的编程语言都有这一机制比如在C++中你可以用extern 告知C++编译器去调用一个C的函数。
4. “A native method is a Java method whose implementation is provided by non-java code.”本地方法是一个非Java的方法它的具体实现是非Java代码的实现
5. 在定义一个native method时并不提供实现体有些像定义一个Java interface因为其实现体是由非java语言在外面实现的。
6. 本地接口的作用是融合不同的编程语言为Java所用它的初衷是融合C/C++程序。
## 举例
需要注意的是标识符native可以与其它java标识符连用但是abstract除外
```java
public class IHaveNatives {
public native void Native1(int x);
public native static long Native2();
private native synchronized float Native3(Object o);
native void Native4(int[] ary) throws Exception;
}
```
## 为什么要使用 Native Method
Java使用起来非常方便然而有些层次的任务用Java实现起来不容易或者我们对程序的效率很在意时问题就来了。
### 与Java环境外交互
**有时Java应用需要与Java外面的硬件环境交互这是本地方法存在的主要原因**。你可以想想Java需要与一些**底层系统**如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制它为我们提供了一个非常简洁的接口而且我们无需去了解Java应用之外的繁琐的细节。
### 与操作系统的交互
1. JVM支持着Java语言本身和运行时库它是Java程序赖以生存的平台它由一个解释器解释字节码和一些连接到本地代码的库组成。
2. 然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一底层系统的支持。这些底层系统常常是强大的操作系统。
3. **通过使用本地方法我们得以用Java实现了jre的与底层系统的交互甚至JVM的一些部分就是用C写的**
4. 还有如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时我们也需要使用本地方法。
### Suns Java
1. Sun的解释器是用C实现的这使得它能像一些普通的C一样与外部交互。jre大部分是用Java实现的它也通过一些本地方法与外界交互。
2. 例如类java.lang.Thread的setPriority()方法是用Java实现的但是它实现调用的是该类里的本地方法setPriority0()。这个本地方法是用C实现的并被植入JVM内部在Windows 95的平台上这个本地方法最终将调用Win32 setpriority() API。这是一个本地方法的具体实现由JVM直接提供更多的情况是本地方法由外部的动态链接库external dynamic link library提供然后被JVM调用。
### 本地方法的现状
目前该方法使用的越来越少了除非是与硬件有关的应用比如通过Java程序驱动打印机或者Java系统管理生产设备在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达比如可以使用Socket通信也可以使用Web Service等等不多做介绍。
# 本地方法栈
1. **Java虚拟机栈于管理Java方法的调用而本地方法栈用于管理本地方法的调用**
2. 本地方法栈,也是线程私有的。
3. 允许被实现成固定或者是可动态扩展的内存大小(在内存溢出方面和虚拟机栈相同)
* 如果线程请求分配的栈容量超过本地方法栈允许的最大容量Java虚拟机将会抛出一个stackoverflowError 异常。
* 如果本地方法栈可以动态扩展并且在尝试扩展的时候无法申请到足够的内存或者在创建新的线程时没有足够的内存去创建对应的本地方法栈那么Java虚拟机将会抛出一个outofMemoryError异常。
4. 本地方法一般是使用C语言或C++语言实现的。
5. 它的具体做法是Native Method Stack中登记native方法在Execution Engine 执行时加载本地方法库。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/chapter_003/0013.png">
**注意事项**
1. 当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
* 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区
* 它甚至可以直接使用本地处理器中的寄存器
* 直接从本地内存的堆中分配任意数量的内存
2. 并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法也可以无需实现本地方法栈。
3. 在Hotspot JVM中直接将本地方法栈和虚拟机栈合二为一。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
---
title: JVM系列-第7章-对象的实例化内存布局与访问定位
tags:
- JVM
- 虚拟机
categories:
- JVM
- 1.内存与垃圾回收篇
keywords: JVM虚拟机。
description: JVM系列-第7章-对象的实例化内存布局与访问定位。
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/JVM/logo.png'
abbrlink: debff71a
date: 2020-11-14 19:38:42
---
对象的实例化内存布局与访问定位
======================
对象的实例化
--------
**大厂面试题**
美团:
1. 对象在`JVM`中是怎么存储的?
2. 对象头信息里面有哪些东西?
蚂蚁金服:
二面:`java`对象头里有什么
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.1.0/JVM/chapter_007/0001.png">
### 对象创建的方式
1. new最常见的方式、单例类中调用getInstance的静态类方法XXXFactory的静态方法
2. Class的newInstance方法在JDK9里面被标记为过时的方法因为只能调用空参构造器并且权限必须为 public
3. Constructor的newInstance(Xxxx):反射的方式,可以调用空参的,或者带参的构造器
4. 使用clone()不调用任何的构造器要求当前的类需要实现Cloneable接口中的clone方法
5. 使用序列化从文件中从网络中获取一个对象的二进制流序列化一般用于Socket的网络传输
6. 第三方库 Objenesis
### 对象创建的步骤
> **从字节码看待对象的创建过程**
```java
public class ObjectTest {
public static void main(String[] args) {
Object obj = new Object();
}
}
```
```
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
LineNumberTable:
line 9: 0
line 10: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 obj Ljava/lang/Object;
}
```
**1、判断对象对应的类是否加载、链接、初始化**
1. 虚拟机遇到一条new指令首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用并且检查这个符号引用代表的类是否已经被加载解析和初始化。即判断类元信息是否存在
2. 如果该类没有加载那么在双亲委派模式下使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的.class文件如果没有找到文件则抛出ClassNotFoundException异常如果找到则进行类加载并生成对应的Class对象。
**2、为对象分配内存**
1. 首先计算对象占用空间的大小接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量仅分配引用变量空间即可即4个字节大小
2. 如果内存规整:采用指针碰撞分配内存
* 如果内存是规整的那么虚拟机将采用的是指针碰撞法Bump The Point来为对象分配内存。
* 意思是所有用过的内存在一边,空闲的内存放另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针往空闲内存那边挪动一段与对象大小相等的距离罢了。
* 如果垃圾收集器选择的是Serial ParNew这种基于压缩算法的虚拟机采用这种分配方式。一般使用带Compact整理过程的收集器时使用指针碰撞。
* 标记压缩(整理)算法会整理内存碎片,堆内存一存对象,另一边为空闲区域
3. 如果内存不规整
* 如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表来为对象分配内存。
* 意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为了 “空闲列表Free List
* 选择哪种分配方式由Java堆是否规整所决定而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定
* 标记清除算法清理过后的堆内存,就会存在很多内存碎片。
**3、处理并发问题**
1. 采用CAS+失败重试保证更新的原子性
2. 每个线程预先分配TLAB - 通过设置 -XX:+UseTLAB参数来设置区域加锁机制
3. 在Eden区给每个线程分配一块区域
**4、初始化分配到的空间**
- 所有属性设置默认值,保证对象实例字段在不赋值可以直接使用
- 给对象属性赋值的顺序:
1. 属性的默认值初始化
2. 显示初始化/代码块初始化(并列关系,谁先谁后看代码编写的顺序)
3. 构造器初始化
**5、设置对象的对象头**
将对象的所属类即类的元数据信息、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现。
**6、执行init方法进行初始化**
1. 在Java程序的视角看来初始化才正式开始。初始化成员变量执行实例化代码块调用类的构造方法并把堆内对象的首地址赋值给引用变量
2. 因此一般来说由字节码中跟随invokespecial指令所决定new指令之后会接着就是执行init方法把对象按照程序员的意愿进行初始化这样一个真正可用的对象才算完成创建出来。
> **从字节码角度看 init 方法**
```java
/**
* 测试对象实例化的过程
* ① 加载类元信息 - ② 为对象分配内存 - ③ 处理并发问题 - ④ 属性的默认初始化(零值初始化)
* - ⑤ 设置对象头的信息 - ⑥ 属性的显式初始化、代码块中初始化、构造器中初始化
*
*
* 给对象的属性赋值的操作:
* ① 属性的默认初始化 - ② 显式初始化 / ③ 代码块中初始化 - ④ 构造器中初始化
*/
public class Customer{
int id = 1001;
String name;
Account acct;
{
name = "匿名客户";
}
public Customer(){
acct = new Account();
}
}
class Account{
}
```
**Customer类的字节码**
```java
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 aload_0
5 sipush 1001
8 putfield #2 <com/atguigu/java/Customer.id>
11 aload_0
12 ldc #3 <匿名客户>
14 putfield #4 <com/atguigu/java/Customer.name>
17 aload_0
18 new #5 <com/atguigu/java/Account>
21 dup
22 invokespecial #6 <com/atguigu/java/Account.<init>>
25 putfield #7 <com/atguigu/java/Customer.acct>
28 return
```
* init() 方法的字节码指令:
* 属性的默认值初始化:`id = 1001;`
* 显示初始化/代码块初始化:`name = "匿名客户";`
* 构造器初始化:`acct = new Account();`
对象的内存布局
---------
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.1.0/JVM/chapter_007/0002.png">
> **内存布局总结**
```java
public class Customer{
int id = 1001;
String name;
Account acct;
{
name = "匿名客户";
}
public Customer(){
acct = new Account();
}
public static void main(String[] args) {
Customer cust = new Customer();
}
}
class Account{
}
```
图解内存布局
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.1.0/JVM/chapter_007/0003.png">
对象的访问定位
---------
**JVM是如何通过栈帧中的对象引用访问到其内部的对象实例呢**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.1.0/JVM/chapter_007/0004.png">
定位通过栈上reference访问
**对象的两种访问方式:句柄访问和直接指针**
**1、句柄访问**
1. 缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问到堆中的对象,效率低
2. 优点reference中存储稳定句柄地址对象被移动垃圾收集时移动对象很普遍时只会改变句柄中实例数据指针即可reference本身不需要被修改
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.1.0/JVM/chapter_007/0005.png">
**2、直接指针HotSpot采用**
1. 优点:直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据
2. 缺点:对象被移动(垃圾收集时移动对象很普遍)时需要修改 reference 的值
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.1.0/JVM/chapter_007/0006.png">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff