【java八股文】之JVM基础篇

【java八股文】之JVM基础篇-CSDN博客

【java八股文】之MYSQL基础篇-CSDN博客

【java八股文】之Redis基础篇-CSDN博客

【java八股文】之Spring系列篇-CSDN博客

【java八股文】之分布式系列篇-CSDN博客

【java八股文】之多线程篇-CSDN博客

【java八股文】之JVM基础篇-CSDN博客

【java八股文】之计算机网络系列篇-CSDN博客

1、Java中都有哪些引用?

强引用 :发生GC时候不会被回收

软引用:发生内存满(内存溢出的时候)会被回收(通常用于缓存)

弱引用:发生下一次GC时候会被回收

虚引用:无法通过虚引用去获取对象。用途:用于gc时返回一个通知

2、JVM运行时数据区域(内存结构)

首先编译器通过把Java代码转换成字节码,类加载器把字节码加载到运行数据区域的方法区内,而字节码只是一套JVM规范指令,所以这个时候需要执行引擎将字节码翻译成底层系统指令,在交给CPU去执行,这个过程需要用到其他语言本地库接口;

2.1 线程私有:

  • 本地方法栈:为native修饰的本地方法提供的空间
  • Java虚拟机栈:每次调用一个方法都会产生一个栈帧,每个栈帧内部有操作数栈,局部变量表 ,动态链接,方法出口等信息
  • 程序计数器:用来记录程序执行的位置记录,方便切回后能够继续执行

2.2 线程共享:

  • 虚拟机堆空间:JVM进行对象的创建存储空间,也是垃圾回收的主要区域,分为新生代和老年代
  • 方法区:存放类信息、静态变量、常量、运行时常量池等信息。JDK1.8之前用的是永久代,之后用的是元空间

3、JVM类加载过程

主要分为 加载 -> 链接 - > 初始化 三步骤:

  • 加载:把字节码通过二进制的方式转化到方法区中的运行数据区
  • 链接:分为三步骤:
    • 验证:校验字节码的正确性
    • 准备:给静态变量分配内存空间 和赋值操作(赋0值 ,比如String 赋值 null int 赋值 0 ……)注意 final修饰的变量不是这个时候赋值的而是在在编译期间就已经赋值了
    • 解析:解析是将常量池中的符号引用替换成直接引用的过程

                符号引用:以一组符号(可以是任何格式的字面量,无歧义即可)来描述所引用的目标。
                直接引用:可以直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

  • 初始化:初始化阶段可以看做是执行类的构造器()方法的过程。

注意:值得注意的是:加载阶段与连接阶段部分内容是交叉进行的,但开始时间有先后顺序。

4、对象的创建过程


4.1 首先进行类的加载检查

虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
new指令对应到语言层面上讲是,new关键词、对象克隆、对象序列化等

4.2 分配内存

对象所需内存的大小在类 加载完成后便可完全确定,为对象分配空间的任务等同于把 一块确定大小的内存从Java堆中划分出来。

这个步骤有两个问题:

        1. 如何划分内存。

        2. 在并发情况下, 可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。

划分内存的方法:

  • “指针碰撞”(Bump the Pointer)(默认用指针碰撞)

如果Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。

  • “空闲列表”(Free List)

如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记 录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

解决并发问题的方法:

  • CAS(compare and swap)

虚拟机采用CAS配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。

  • 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)

把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存。通过 XX:+/UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启 XX:+UseTLAB), XX:TLABSize 指定TLAB大小。

4.3 初始化

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

4.4 设置对象头

初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)
和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

4.5 执行方法

执行方法,即对象按照程序员的意愿进行初始化。对应到语言层面上讲,就是为属性赋值(注意,这与上面的赋零值不同,这是由程序员赋的值),和执行构造方法。

4.6 如何定位一个对象多大的空间?


在HotSpot虚拟机里对象内存布局分为3个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

4.6.1 对象头

  • 第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特, 官方称它为“Mark Word”。
  • 另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

如果对象是一个 java 数组,那么在对象头中还有一块用于记录数组长度的数据。

案例:
1)创建一个maven项目,引入依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.16</version>
</dependency>


2)新建一个类

@Data
public class BaseEntity  {

    private long id;

    private double amount;

    private int updateUserId;

    private float f;

    private char c;

    private byte b;

    private boolean bb;

    private short ss;

    private long[] list; //4byte

    private String s; //4byte

    private Long count; //4byte
}


3)测试

public class ObjectHeaderTest {

    public static void main(String[] args) {
        BaseEntity baseEntity = new BaseEntity();
        String toPrintable = ClassLayout.parseInstance(baseEntity).toPrintable();
        System.out.println(toPrintable);
    }
}


打印结果

site.sunlong.obj.BaseEntity object internals:
OFF  SZ               TYPE DESCRIPTION               VALUE
  0   8                    (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                    (object header: class)    0xf800c143
 12   4                int BaseEntity.updateUserId   0
 16   8               long BaseEntity.id             0
 24   8             double BaseEntity.amount         0.0
 32   4              float BaseEntity.f              0.0
 36   2               char BaseEntity.c               
 38   2              short BaseEntity.ss             0
 40   1               byte BaseEntity.b              0
 41   1            boolean BaseEntity.bb             false
 42   2                    (alignment/padding gap)   
 44   4             long[] BaseEntity.list           null
 48   4   java.lang.String BaseEntity.s              null
 52   4     java.lang.Long BaseEntity.count          null
Instance size: 56 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total


4)测试结果解读

数据第1行(object header: mark):这个是Mark Word占用的字节数,64位机器上占用8个字节,32位机器上占用4个字节。

数据第2行(object header: class) :类型指针,在开启指针压缩的情况下占4个字节,未开启的情况下占8个字节,jdk1.6之后默认开启指针压缩。

数据第3-14行(除(alignment/padding gap)):BaseEntity对象属性类型占用字节数,一共占用39个字节。

其他数据行:对齐填充2个字节,由于Mark Word(8个字节)+类型指针(4个字节)+对象字节数(42个字节)=54个字节,54不是8的倍数,所以要填充2个字节凑够8的倍数。如果字节数之和刚好是8的倍数,则不需要对齐填充。

4.7 JVM类初始化顺序

父类静态代码块和静态成员变量->子类静态代码块和静态成员变量->父类代码块和普通成员变量->父类构造方法->子类代码块和普成员变量->子类构造方法

5、什么是内存泄漏?

就是不再使用的对象或者变量一直占用着内存中,而且GC也无法去回收。这种情况是因为长生命周期的对象持有短生命周期的对象的引用就会发生内存泄漏情况。

什么情况下会内存溢出?
堆内存溢出:

(1)当对象一直创建而不被回收时

(2)加载的类越来越多时

(3) 虚拟机栈的线程越来越多时

栈溢出:方法调用次数过多,一般是递归不当造成

6、简述一下Java的垃圾回收机制?

在Java中,程序员不需要去指定的释放对象。而是有JVM虚拟机自行的执行,有一个垃圾回收线程(守护线程)他的优先级比较低,他不会去主动的去执行,只有虚拟机空闲或者当前的堆空间满的时候,才会去触发,去扫描回收掉没有用的对象。

当创建一个对象的时候GC就会进行监控这个对象的地址、大小以及使用情况 。GC通过有向图的方式记录管理堆,通过这种方式来判断那些可达,哪些不可达,从而来判定是否回收。

程序员可以手动的进行System.gc()来通知gc运行,但是GC并不能保证GC一定会执行

7、如何判断是否能够回收?

两种方式:

  • 引用计数器法:但是不能避免循环引用问题
  • 可达性分析算法:当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

8、垃圾回收算法有哪几种?

  • 标记清除: 标记需要清除的对象,对标记的对象进行清楚操作。缺点:产生了大量的不连续碎片化对象,不利于分配新的对象。
  • 标记整理:标记需要清除的对象,对标记的对象进行清除操作过程中,也将没有清除的对象进行整理到内存的一端。优点:解决了标记-清理算法存在的内存碎片问题 缺点:需要局部的移动对象,降低的效率
  • 复制算法:将内存空间分成等大的两部分,每次遍历一个区域将存货的对象放入另一个区域,然后清除掉掉这个区域所有的对象,以此类推循环。 优点:效率高速度快,也没有内存碎片的问题 缺点:堆内存大小要求高
  • 分代算法:就是把内存空间分配成不同的年轻代、老年代和永久代,根据不同的年代使用不同的算法。比如说年轻代空间大而且垃圾回收发生频繁所以采用复制算法,老年代就采用标记整理算法

9、垃圾收集器

Serial收集器:色而瑞
​ 新生代采用复制算法,老年代采用标记-整理算法。单线程,所以在发生GC时候需要暂停所有线程的工作。

Parallel Scavenge收集器:帕尔雷尔
​ 新生代采用复制算法,老年代采用标记-整理算法。多线程,Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)

ParNew收集器:帕尔new(jdk8默认设置的垃圾收集器)
​ 新生代采用复制算法,老年代采用标记-整理算法。和Parallel 相似主要用于配合CMS收集器使用。

CMS收集器:
​ CMS收集器是一种 “标记-清除”算法实现的。

  • ​ CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户 体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用 户线程(基本上)同时工作。
  • 初始标记: 暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
  • 并发标记: 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。
  • 重新标记:重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法(见下面详解)做重新标记。
  • 并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)。
  • 并发重置:重置本次GC过程中的标记数据。


10、内存对象的分配策略?

对象的内存分配通常是在 Java堆上分配(随着虚拟机优化技术的诞生,某些场 景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区, 如果启动了本地线程缓冲,将按照线程优先在TLAB 上分配。少数情况下也会直 接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种

  • 对象优先在 Eden 区分配

多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行 分配时,虚拟机将会发起一次 Minor GC。如果本次 GC后还是没有足够的空 间,则将启用分配担保机制在老年代中分配内存。

年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间,如果空间不够的话就直接进行FullGC

  • 大对象直接进入老年代

所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导 致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对 象。前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象 直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。

  • 长期存活对象将进入老年代

虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对 象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被 移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬 过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15)就会被晋升 到老年代。

  • 空间分配担保
  • 动态年龄判定

11、说一说如何理解双亲委派模型


类加载器主要有:

  • 引导类加载器(BootstrapClassloader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
  • 扩展类加载器(ExtceptClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
  • 应用程序类加载器(ApplicationClssloader):负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类

双亲委派模型就是子加载器向父级加载器向上委托它加载,如果父加载器能加载那么子加载器就不用加载直接返回,如果不能加载就有子加载器进行加载。

这样做的好处就是:

  • 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

如何打破双亲委派模型:重写loadClass(String, boolean)方法
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/325387.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CentOS7安装MySQL 错误:软件包:akonadi-mysql-1.9.2-4.el7.x86_64 (@anaconda)

问题如图所示 运行一下命令解决 yum -y remove mariadb-libsyum install mysql-community-server

Linux知识(未完成)

一、Linux 1.1 Linux 的应用领域 1.1.1 个人桌面领域的应用 此领域是 Linux 比较薄弱的环节但是随着发展&#xff0c;近几年 linux 在个人桌面领域的占有率在逐渐提高 1.1.2 服务器领域 linux 在服务器领域的应用是最高的 linux 免费、稳定、高效等特点在这里得到了很好的…

Python读取modbus数据(WTVB01-485振动传感器)

Python读取modbus数据&#xff08;WTVB01-485振动传感器&#xff09; 实物如下图&#xff1a; 参考手册上获取到的部分信息&#xff1a; 接下来开始操作&#xff1a; 1.将485转USB口连接到计算机&#xff0c;然后使用&#xff1a;Modbus Poll这个软件与实物通讯。这里注意&a…

防泄密之巅,厨电安全无忧:迅软DSE引领科技保密新潮流

客户简要介绍 某股份有限公司从创立至今一直专注厨电领域&#xff0c;始终以产品创新为企业战略重心&#xff0c;为全家万户提供厨卫及家居定制。公司产品涵盖集成烹饪中心、抽油烟机、燃气灶具、燃气热水器、电热水器&#xff0c;为全球多个国家和地区的消费者提供高品质的服…

vue-quill-editor富文本插入图片改为上传服务器

1.安装vue-quill-editor npm install vue-quill-editor 2.安装依赖 npm install quill 3.代码引用 import { quillEditor } from vue-quill-editor; // 引入Qill插件 import Quill from quill; import quill/dist/quill.core.css; // import styles import quill/dist/quill.s…

matlab行操作快?还是列操作快?

在MATLAB中&#xff0c;通常情况下&#xff0c;对矩阵的列进行操作比对行进行操作更有效率。这是因为MATLAB中内存是按列存储的&#xff0c;因此按列访问数据会更加连续&#xff0c;从而提高访问速度。 一、实例代码 以下是一个简单的测试代码&#xff0c; % 测试矩阵大小 ma…

【GitHub】如何上传文件夹到GitHub上(配图详解)

一、如果没有账号要先创建账号&#xff08;有账号跳过此步骤&#xff09;二、建立一个仓库&#xff08;有仓库跳过此步骤&#xff09;三、复制仓库地址四、以下为本地操作 1、在本地新建一个空文件夹2、上传文件 2.1、在空文件夹内&#xff0c;右键选择Git Bash Here2.2、弹出G…

京东ES支持ZSTD压缩算法上线了:高性能,低成本 | 京东云技术团队

1 前言 在《ElasticSearch降本增效常见的方法》一文中曾提到过zstd压缩算法[1]&#xff0c;一步一个脚印我们终于在京东ES上线支持了zstd&#xff1b;我觉得促使目标完成主要以下几点原因&#xff1a; Elastic官方原因&#xff1a;zstd压缩算法没有在Elastic官方的开发计划中&…

屏幕录制软件有哪些?强烈推荐5款免费好用录屏软件

录制全屏视频是制作视频教程、评论、游戏等内容的绝佳方式。它可以包括您计算机屏幕上的任何活动以及您的音频和网络摄像头。特别是&#xff0c;屏幕录像机对喜欢创建软件评论的视频博主很有帮助。如果您是其中之一&#xff0c;那么好消息是您可以在网络上找到很多屏幕录制应用…

基于 IDEA 进行 Maven 依赖管理

一、依赖管理概念 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中&#xff0c;避免出现版本冲突和依赖缺失等…

「工业遥测」图表控件LightningChart在制造加工业中的应用

LightningChart.NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科学…

基于LLM大模型的结构化数据批量提取

在当今世界&#xff0c;越来越多的组织致力于数据驱动的决策。 然而&#xff0c;他们通常面临着从非结构化文本数据&#xff08;例如客户评论或反馈&#xff09;中提取有价值的见解的挑战。 这篇文章是为想要使用非结构化数据获得有用见解的数据科学家、分析师和决策者量身定制…

电子版试卷怎么打印出来?分享3个扫描小技巧

电子版试卷怎么打印出来&#xff1f;在日常学习中&#xff0c;将试卷打印成电子版可以带来很多方便。首先&#xff0c;电子版试卷方便存储和查找&#xff0c;可以随时随地使用电脑或手机进行阅读和复习。其次&#xff0c;电子版试卷方便进行标注和分析&#xff0c;可以使用不同…

提供多语种客户服务的正确方法:让你更接近全球客户

优质的客户支持是任何成功企业的核心。每位客户都希望得到全天候的及时响应。事实上&#xff0c;根据《哈佛商业评论》的研究报告&#xff0c;快速响应会促使人们在未来支付更多的费用。此外&#xff0c;在与全球客户打交道时&#xff0c;您的沟通必须超越语言障碍。用客户的语…

2-Linux-应用-部署icwp-Linux虚拟机【Django+Vue+Nginx+uwsgi+Linux】

本文概述 本文章讲述基于Linux CentOS 7系统&#xff08;虚拟机&#xff09;&#xff0c;部署DjangoVue开发的前后端分离项目。 项目源码不开放&#xff0c;但是操作步骤可以借鉴。 该文章将项目部署在Linux虚拟机上&#xff0c;暂不使用Docker 相关指令尽量展示执行路径&am…

python下常用的爬虫模块

目录 一&#xff1a;requests 二&#xff1a;BeautifulSoup 三&#xff1a;Scrapy 四&#xff1a;Selenium 一&#xff1a;requests requests 是一个用于发送 HTTP 请求的 Python 库。它提供了简洁的 API 来发送各种类型的 HTTP 请求&#xff0c;如 GET、POST、PUT、DELETE…

多级缓存架构(二)Caffeine进程缓存

文章目录 一、引入依赖二、实现进程缓存1. 配置Config类2. 修改controller 三、运行四、测试 通过本文章&#xff0c;可以完成多级缓存架构中的进程缓存。 一、引入依赖 在item-service中引入caffeine依赖 <dependency><groupId>com.github.ben-manes.caffeine…

过滤器、拦截器、AOP、ControllerAdvcie执行顺序对比

过滤器Filter 简介 来自J2EE中的Servlet技术实现原理&#xff1a;基于servlet的函数回调实现只可以获取到请求中的request和response&#xff0c;无法获取到响应方法的信息可以拦截所有请求支持使用xml配置和注解配置应用场景&#xff1a;权限认证、敏感词检测、访问日志记录…

【Python机器学习系列】建立随机森林模型预测心脏疾病(完整实现过程)

这是Python机器学习系列原创文章&#xff0c;我的第200篇原创文章。 一、引言 对于表格数据&#xff0c;一套完整的机器学习建模流程如下&#xff1a; 针对不同的数据集&#xff0c;有些步骤不适用即不需要做&#xff0c;其中橘红色框为必要步骤&#xff0c;由于数据质量较高&…

【国产mcu填坑篇】华大单片机(小华半导体)一、SPI的DMA应用(发送主机)HC32L136

最近需要用华大的hc32l136的硬件SPIDMA传输&#xff0c;瞎写很久没调好&#xff0c;看参考手册&#xff0c;瞎碰一天搞通了。。。 先说下我之前犯的错误&#xff0c;也是最宝贵的经验&#xff0c;供参考 没多看参考手册直接写&#xff08;即使有点烂仍然提供了最高的参考价值。…