侧边栏壁纸
  • 累计撰写 781 篇文章
  • 累计创建 1 个标签
  • 累计收到 1 条评论
标签搜索

jvm

Dettan
2022-03-04 / 0 评论 / 0 点赞 / 28 阅读 / 4,716 字
温馨提示:
本文最后更新于 2022-04-30,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
/ 后端 / jvm
官方 jvm 规格说明

输出常量池
javap -verbose Test.cass
JVM内存
thread local
pc program count
VM stack 虚拟机栈,

虚拟机栈(线程私有数据区)
每个Java虚拟机线程都有自己的Java虚拟机栈,Java虚拟机栈用来存放栈帧,每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
(线程共享数据区)
在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,new 出来的对象都放在堆里),几乎所有的对象实例都在这里分配。
方法区(线程共享数据区)
方法区在虚拟机启动的时候被创建,它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括在类、实例、接口初始化时用到的特殊方法。在JDK8之前永久代是方法区的一种实现,而JDK8元空间替代了永久代,永久代被移除,也可以理解为元空间是方法区的一种实现。
常量池(线程共享数据区)
常量池常被分为两大类:静态常量池和运行时常量池。
静态常量池也就是Class文件中的常量池,存在于Class文件中。
运行时常量池
(Runtime Constant Pool)是方法区的一部分,是 class 文件中每一个类或者接口的常量池表在运行时的表现形式。在加载类和接口到虚拟机后,就创建对应的运行时常量池。如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,Java 虚拟机就会抛出一个 OutOfMemoryError 异常。
运行时常量池(Runtime Constant Pool)是方法区的一部分,存放一些运行时常量数据。


在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。

⭐ 执行 main 方法的步骤如下:
编译好 App.java 得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载
JVM 找到 App 的主程序入口,执行 main 方法
这个 main 中的第一条语句为 Student student = new Student("tellUrDream") ,就是让 JVM 创建一个Student对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中
加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有 指向方法区中的 Student 类的类型信息 的引用
执行 student.sayName(); 时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
执行 sayName()


内存
堆里存放 new 出的东西.实例化的类加载到堆里,通过地址对其调用
类的方法只在堆里放一个地址引用.
方法区里存不变的东西,如方法,static修饰的东西,class文件
栈是跟踪方法调用的
将一个类变量复制给另一个类赋值的是地址,所以它俩指向的是同一个类.
使用new创建的对象在作用之外不会回收,只要还有变量指向它,它就会一直存在.在没有变量指向它时,垃圾收集器 就会自动回收内存.

类放在堆中
年轻代又分为 Eden 和 Survivor 区。Survivor 区由 FromSpace 和 ToSpace 组成。Eden 区占大容量,Survivor 两个区占小容量,默认比例是 8:1:1。
方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。 常数池,源代码中的命名常量、String常量和static变量保存在方法区。
常量池是方法区的一部分。
3、引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
4、字符串常量在 DATA 区域分配 ,this 在堆空间分配;
5. 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
堆里放的对象
操作数栈里放加法要用的数,return 的值等。

方法区(区空间) 是放代码的地方,加载的类放在这
垃圾回收主要针对堆,堆是所有线程共用的。只有一个堆。

堆构成
Eden、Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;
monor gc eden里是新生的对象, 里面内容达到阙值之后放到右边然后age设为1,执行一次新生代里的所有对象age加一,age到15之后移到右边老年代。
full gc 老年代满了就会自动清,有强引用的不会清。

stw

新生代 老年代 from to

java visulVN 能看垃圾回收的细节的软件


4).每个JVM的线程都有自己的私有的栈空间,随线程创建而创建,java 的stack存放的是frames,java 的stack 和c的不同,只是存放本地变量,返回值和调用方法,不允许直接push和pop frames ,因为frames 可能是有heap 分配的,所以java的stack分配的内存不需要是连续的。java的heap是所有线程共享的,堆存放所有 runtime data , 里面是所有的对象实例和数组,heap是JVM启动时创建。






NIOServer

unix网络编程

rtsp 持续流传输


4. HotSpot 虚拟机对象探秘
通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。
① 对象的创建
下图便是 Java 对象的创建过程:
📋 Step1: 类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
📋 Step2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
内存分配的两种方式:👇
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
❓ 内存分配并发问题
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全
CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
TLAB: 为每一个线程预先在 Eden 区分配一块内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
📋Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
📋Step4:设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
📋 Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
② 对象的内存布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头实例数据 和 对齐填充
Hotspot 虚拟机的对象头包括两部分信息第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
③ 对象的访问定位
建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有 使用句柄 和 直接指针两种:
句柄: 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。
0

评论区