12.JVM —— 直接内存
直接内存Direct Memory不是虚拟机运行数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域
直接内存是在Java堆外的,直接想系统申请的内存区间
NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础
来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存
通常,访问直接内存的速度优于Java堆内存,即读写性能高
因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存
Java的NIO库允许Java程序使用直接内存,用于数据缓冲区
使用下面代码,直接分配本地内存空间
12int BUffER = 1024 * 1024 * 1024; // 1GBByteBuffer byteBuffer = ByteBuffer.allocteDirect(BUFFER);
非直接缓存区和缓存区原来采用BIO的架构,我们需要从用户态切换成内核态
NIO的方式使用了缓存区的概念
存在的问题也可能导致OOM异常
由于直接内存在Java堆外,因此它的大小不会直接首先-XMX ...
11.JVM —— 对象实例化内存布局与访问定位
对象实例化内存布局与访问定位对象实例化面试题
对象在JVM是怎样存储的?
对象头信息里面有哪些东西?
Java对象头有什么?
从对象创建的方式 和 步骤开始说
对象创建方式
new : 最常见的方式、单例类中调用getInstance的静态方法,xxxFactory的静态方法
Class的newInstance方法:在JDK9里面被标记为过时方法,因为只能调用空参构造器
Constructor的newInstance(xxx):反射的方式,可以调用空参的,或者代参的构造器
使用clone():不调用任何的构造器,要求当前的类需要实现Cloneable接口中的clone接口
使用序列化:序列化一般用于Socketde 网络传输
第三方库Objenesis
创建对象的步骤判断对象对应的类是否加载、链接、初始化虚拟机遇到一条new指令,首先去检查这个指令的参数是否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载,解析和初始化。(即判断类元信息是否存在)。如果没有,那么在双亲委派模式下,适用当前类加载器以ClassLoader + 包名 + 类 ...
10.JVM —— 方法区
方法区前言
从线程共享与否的角度来看
ThreadLocal:如何保证多个线程在并发环境下的安全性?典型应用就是数据库连接管理,以及会话管理
栈、堆、方法区的交互关系下面就涉及了对象的访问定位
Person: 存放在元空间,也可以说方法区
person: 存放在Java栈的局部变量表中
new Person(): 存放在Java堆中
方法区的理解《Java虚拟机规范》中明确说明:“尽管所有的方法在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆)
所以,方法区看做是一块独立于Java堆的内存空间
方法区主要存放的是Class,而堆中主要存放的是实例化的对象
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域
方法区在Jvm启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的
方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展的
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟 ...
9.JVM —— 堆
堆堆的核心概念堆针对一个JVM进程来说是唯一的,也就是一个进程只有一个JVM,但进程包含多个线程,他们是共享同一堆空间的。
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
Java堆区在JVM启动的时候即被创建,其空间的大小也就确定了,是JVM管理的最大一块内存空间。
堆内存大小是可以调节的。
《Java虚拟机规范》规定,堆可以处于物理上不连续的内训空间中,但在逻辑上它应该被视为连续的。
所有线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB)
-Xms10m:最小堆内存
-Xmx10m:最大堆内存
下图就是使用: Java VisualVM查看堆空间的内容,通过JDK bin提供的插件
《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is alloc ...
8.JVM —— 本地方法接口
本地方法接口什么是本地方法简单的讲,一个Native Methodt是一个Java调用非Java代码的接口。一个Native Method是这样一个Java方法,该方法的实现由非Java语言实现,比如C。这个特征并非Java所持有,很多其他的编程语言,都有这一机制,比如C++中,你可以用extern “c “告知编译器去调用一个c的函数
“A native method is a Java method whose implementation is provided by non-java code.”(本地方法是一个非Java的方法,它的具体实现是非Java代码的实现)
在定义一个native method时,并不提供实现体(有些像定义一个Java interface),因为其实现体是由非java语言在外面实现的。
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序。
代码举例说明Native方法是如何编写的
123456public class IhaveNatives{ public native void Native1(int ...
7.JVM —— 虚拟机栈
虚拟机栈虚拟机栈概述由于跨平台的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计基于寄存器的。
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
有不少Java开发人员一提到Java内存结构,就会非常粗粒度的将JVM中的内存区理解为仅有Java堆(heap)和Java栈(stack)为什么?
首先栈是运行时的单位,而堆是存储的单位
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据
堆解决的是数据存储的问题,即数据怎么放,放在那里。
Java虚拟机栈是什么Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。
线程私有
生命周期声明周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了。
作用主管Java程序的运行,它保存方法的局部变量,部分结果,并参与方法的调用和返回。
局部变量,它是相比于成员变量来说的(或属性)
基本数据类型变量 VS 引用类型变 ...
6.JVM —— 程序计数器
程序计数器介绍JVM中的程序计数器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有吧数据装在寄存器才能够运行。这里,并非是广义上所指的物理寄存器,或许将其翻译为PC寄存器(或指令寄存器)会更加贴切(也称为程序钩子),并且不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。
它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域。
在JVM规范中,每个线程都会有自己的额程序计数器,是线程私有的,生命周期与线程的声明周期保持一致。
任何时间一个线程都是只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址:或者是执行native方法,则是未指定值(undefend).
他是唯一一个在Java虚拟机规范中没有规定任何outMemoryError情况的区域。
作用PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
代码演示1234567public class PCRe ...
5.JVM —— 运行时数据区概述及线程
运行时数据区概述及线程前言本节主要讲的是运行时数据区,也就是下图这部分,他是类加载完成后的阶段
当我们通过前面的:类的加载->验证->准备->解析->初始化 这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区。
也就是大厨做饭,我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品
内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序实时运行,JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高校稳定运行。不同JVM对于内存的划分方式和管理机制存在着部分差异。
我们通过磁盘或者网络IO得到的数据,都需要先加载到内存中,然后CPU从内存中获取数据进行读取,也就是说内存充当了CPU和磁盘之间的桥梁。
运行时数据区的完整图
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数 ...
4.JVM —— 类加载子系统
类加载子系统概述
完整图如下
如果自己想手写一个Java虚拟机的话,主要考虑哪些结构呢?
类加载器
执行引擎
类加载器子系统作用类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,至于他是否可以运行,则有Execution Engine决定。
加载的信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
class file存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
class file加载到JVM中,被称为DNA元数据模板,放在方法区。
在.class文件->JVM->最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一个快递员的角色。
类的加载过程例如下面的一段简单的代码:
12345public clas ...
8.CAS & ABA & AQS
CAS什么是CASCAS(Compare And Swap)指比较并交换。CAS算法CAS(V,E, N)包含3个参数,V表示要更新的变量,E表示预期的值,N表示新值。在且仅在V值等于 E值时,才会将V值设为 N,如果 V值和 E值不同,则说明已经有其他线程做了更新,当前线程什么都不做。最后,CAS返回当前V的真实值。
CAS的特性:乐观锁CAS操作采用了乐观锁的思想,总是认为自己可以成功完成操作。在有多个线程同时使用CAS操作一个变量时,只有一个会胜出并成功更新,其余均会失败。失败的线程不会被挂起,仅被告知失败,并且允许再次尝试,当然,也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
CAS自旋等待在JDK的原子包java.util.concurrent.atomic里面提供了一组原子类,这些原子类的基本特性就是在多线程环境下,在有多个线程同时执行这些类的实例包含的方法时,会有排他性。其内部便是基于CAS算法实现的,即在某个线程进入方法中执行其中的指令时,不会被其他线程打断;而别的线程就像自旋锁一样,一直等到该方法 ...