《java虚拟机》必知必会的14个问题总结(内存模型+gc)(代码片段)

HotAutumn HotAutumn     2022-12-06     484

关键词:

一、Java 概述

1、Java 相较于 PHP、C#、Ruby 等一样很优秀的编程语言的优势是什么?

(1)体系结构中立,跨平台性能优越。Java 程序依赖于 JVM 运行,javac 编译器编译Java程序为平台通用的字节码文件(.class),再由 JVM 与不同操作系统匹配,装载字节码并解释(也有可能是编译,会在第三个问题中说到)为机器指令执行。

(2)安全性优越。通过 JVM 与宿主环境隔离,且 Java 的语法也一定程度上保障了安全,如废弃指针操作、自动内存管理、异常处理机制等。

(3)多线程。防止单线程阻塞导致程序崩溃,分发任务,提高执行效率。

(4)分布式。支持分布式,提高应用系统性能。

(5)丰富的第三方开源组件。SpringStrutsHibernate、Mybatis、Quartz 等等等等。

2、字节码是什么? .class 字节码文件是什么?

(1)字节码是包含 Java 内部指令集、符号集以及一些辅助信息的能够被 JVM 识别并解释运行的符号序列。字节码内部不包含任何分隔符区分段落,且不同长度数据都会构造成 n 个 8 位字节单位表示。

(2).class 里存放的就是 Java 程序编译后的字节码,包含了类版本信息、字段、方法、接口等描述信息以及常量池表,一组8位字节单位的字节流组成了一个字节码文件。

3、JVM 是什么?HotSpot 虚拟机有什么特点?

JVM 全称 Java Visual Machine,Java 虚拟机。是 Java 程序的运行环境,主要负责装载字节码文件,并解释或编译成对应平台的机器指令执行。

我们使用最多的是 JDK 缺省自带的 HotSpot 虚拟机,使用解释器加编译期并存架构方案。一开始的时候使用解释器,使编译未结束时就可以解释字节码为本地机器指令执行,提高效率。编译器用在 HotSpot 的热点探索功能上,在存在频繁调用的方法或循环次数较多的代码时,就会把这类代码块标记为 “热点代码”,通过内嵌的双重 JIT(Just in time compiler)将字节码直接编译成对应机器指令,以提高效率。

二、Java 内存模型

1、PC 计数器

线程私有,用于记录当前线程正在执行字节码的地址,如果执行的是 native 本地方法,PC 计数器为空。

2、Java 栈

线程私有,也叫作 Java 虚拟机栈,用于存储栈帧,栈帧的入栈出栈过程即方法调用到执行结束的过程。栈帧中主要存放方法执行所需的局部变量表(包括局部变量的声明数据类型、对象引用等)、操作数栈、方法出口等信息

3、本地方法栈

与 Java 栈功能类似,只是用于存储 native 本地方法的相关信息。

4、Java 堆

线程公用,用于存放对象实例,包括数组,也叫 GC 区,是 GC 主要工作的区域。也正是如此,由于 GC 频率过快与效率不高,堆区的可能成为 JVM 性能瓶颈,于是考虑到性能,堆区不再是对象内存分配的唯一选择。这里就涉及到了对象的逃逸分析与栈上分配。

逃逸分析就是用来分析对象的作用域是否在方法内部,当方法返回了当前类实例对象、方法中为当前类成员变量赋值、方法中引用当前类成员变量的值时就会发生逃逸,依然在堆上分配内存。但当对象的作用域就在方法内时,比如在方法内创建了该类的实例,没有返回、没有引用,则这种情况就直接在 Java 栈上分配内存,随着栈帧的出栈释放空间,减轻了堆区GC 的压力。

5、方法区

线程公用,存储了每一个 Java 类的结构信息,比如:字段、各种方法的字节码内容数据、运行时常量池等。方法区也被称为永久带。一般没有显示要求,GC 只对方法区中的常量池回收以及类型卸载。

6、运行时常量池

属于方法区的一部分,类加载器将类的字节码文件加载如 JVM 中后,会把字节码文件中的常量池表转化为运行时常量池。

三、Java 垃圾回收机制

(1)引用计数法:每个对象都创建一个私有的引用计数器,当该对象被其他对象引用时(出现在等号右边),引用计数器加 1;当不再引用时,引用计数器减 1;当引用计数器为 0 时,对象即可被回收。这种方式存在着当两个对象互相引用时,二者引用计数器值都不为 0 无法被回收的问题;

(2)根搜索算法:JVM 一般使用的标记算法,把对象的引用看作图结构,由根节点集合出发,不可达的节点即可回收,其中根节点集合包含的如下 5 种元素:

1、Java 栈中的对象引用;

2、本地方法栈中的对象引用;

3、运行时常量池的对象引用;

4、方法区中静态属性的对象引用;

5、所有 Class 对象;

2、常见的垃圾回收算法有哪些?JVM 使用哪种?

(1)标记-清除算法:分两个阶段执行,第一个阶段标记可用对象,第二个阶段清除垃圾对象;这个方法很基础简单,但效率低下,而且会产生内存碎片(不连续的内存空间),无法再次分配给较大对象。

(2)复制算法:被广泛用于新生代对象的回收。将内存分为两个区域,新对象都分配在一个区域中,回收时将可用对象连续复制到另一个区域,回收完成后,新对象分配在有对象的区域,循环往复。这种算法不会产生内存碎片,且效率较高,但因为同时只有一个区域有效,会导致内存利用率不高。

(3)标记-整理算法:被应用于老年代对象的回收。这种算法与标记清除算法类似,第一个阶段标记可用对象,第二个阶段将可用对象移动到一段连续的内存上,解决了标记-清除算法会产生内存碎片的缺点。

(4)分代回收算法:在 HotSpot 虚拟机中,基于分代的特点(堆内存可进一步分为年轻代、老年代,老年代存放存活时间较长的对象),JVM GC 使用分代回收算法。

年轻代使用复制算法:分为一个较大的 Eden 区与两个较小的、等大小的 Survivor 区(From Space 与 To Space),比例一般是 8:1:1。新对象都分配在 Eden 区,当 GC 发生时(新生代的 GC 一般叫做 Minor GC),将 Eden 区与 From 区中的可用对象复制到 To 区中,From Space 与 To Space 互换名称,循环方法。直到发生如下两种情况,对象进入老年代:

1' From 区内的对象已经达到存活代数阀值(经过 GC 的次数达到设定值),GC 时不会进入 To 区中,直接移动至老年代;

2' 在回收 Eden 区与 From 区后,超出 To 区可容纳范围,则直接将存活对象移动至老年代。

老年代使用标记-整理算法:当老年代满的时候,会触发 Full GC(新生代与老年代一起进行 GC)。

3、常见的垃圾回收器有哪些?有什么特点?适合应用与什么场景?

(1)Serial 收集器

年轻代采用复制算法、串行回收、与 “Stop the world” 机制(GC 时停止其他一切工作),适用于单核 CPU 环境,绝对不推荐应用于服务器端。

Serial 提供了老年代的回收器 Serial Old,采用标记-整理算法,其他特性与新生代一致。

Serial+Serial Old 适合客户端场景。

(2)ParNew 收集器

相当于 Serial 的多线程版本,并行回收,年轻代同样采用复制算法与 “Stop the world” 机制,适用于多核 CPU、低延迟环境,推荐应用于服务器场景。

(3)Parallel 收集器

与 ParNew 类似,复制算法、并行回收、“Stop the world” 机制,但是与 ParNew 不同,Parallel 可以控制程序吞吐量大小,也被称为吞吐量优先的垃圾收集器。

与 Serial 类似,Parallel 也有老年代版本,Parallel Old,同样采用标记整理-算法。

Parallel+Parallel Old 非常适用于服务器场景。

(4)CMS 收集器

与 Parallel 的高吞吐对应,CMS 就是为高并发、低延时而生的。采用标记-清除算法、并行回收、“Stop the world”。因为采用了标记-清除算法,会产生大量内存碎片,要慎重使用。

(5)G1 收集器

是一款基于并行、并发、低延时、暂停时间可控的区域化分代式垃圾回收器。

具有革命意义的设计,放弃了堆区年轻代、老年代的划分方案,而是将堆区或分成约 2048 个大小相同的独立 Region 块。

4、GC 的优化方案?

基本的原则就是尽可能地减少垃圾和减少 GC 过程中的开销。其中需要注意,JVM 进行次 GC 的频率很高,但因为 Minor GC 占用时间极短,所以对系统产生的影响不大。更值得关注的是 Full GC 的触发条,具体措施包括以下几个方面:

(1)不要显式调用 System.gc

调用 System.gc 也仅仅是一个请求(建议)。JVM 接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。但即便这样,很多情况下它会触发 Full GC,也即增加了间歇性停顿的次数。

(2)尽量减少临时对象的使用

临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,也就减少了 Full GC 的概率。

(3)对象不用时最好显式置为 NULL

一般而言,为 NULL 的对象都会被作为垃圾处理,所以将不用的对象显式地设为 NULL,有利于 GC 收集器判定垃圾,从而提高了 GC 的效率。

(4)尽量使用 StringBuffer,而不用 String 来累加字符串

由于 String 是常量,累加 String 对象时,并非在一个 String 对象中扩增,而是重新创建新的 String 对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作 “+” 操作时都必须创建新的 String 对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用 StringBuffer 来累加字符串,因 StringBuffer 是可变长的,它在原有基础上进行扩增,不会产生中间对象。

(5)能用基本类型如 int、long,就不用 Integer、Long 对象

基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

(6)尽量少用静态对象变量

静态变量属于全局变量,不会被 GC 回收,它们会一直占用内存。

(7)分散对象创建或删除的时间

集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM 在面临这种情况时,只能进行 Full GC,以回收内存或整合内存碎片,从而增加主 GC 的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主 GC 的机会。

5、Java 即使有了 GC 也会出现的内存泄漏情况?举例说明。

1. 静态集合类像 HashMap、Vector 等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象 Object 也不能被释放,因为他们也将一直被 Vector 等应用着。

Static Vector v = new Vector;
for(int i = 1; i<100; i++) 
    Object o = new Object;
    v.add(o);
    o = null;

在这个例子中,代码栈中存在 Vector 对象的引用 v 和 Object 对象的引用 o。在 for 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为,GC 在跟踪代码栈中的引用时,会发现v引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管 o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后,Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

2.各种连接,数据库连接,网络连接,IO 连接等没有显示调用 close 关闭,不被 GC 回收导致内存泄露。

3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。


转自这个地方

必知必会的设计原则——合成复用原则(代码片段)

 设计原则系列文章 必知必会的设计原则——单一职责原则必知必会的设计原则——开放封闭原则必知必会的设计原则——依赖倒置原则必知必会的设计原则——里氏替换原则必知必会的设计原则——接口隔离原则必知必... 查看详情

java开发者必知必会的50个redis知识点,面试/学习都无所畏惧

推荐阅读关于"高并发系统设计"看这篇就够了,阿里、百度、美团都在用Redis作为目前的主流NoSql数据库,不会是不可能的,在面试中也是非常高频的,一定不能在这个环节丢分,不管是学习,还是面试,以下知识点,都... 查看详情

java开发者必知必会的50个redis知识点,面试/学习都无所畏惧(代码片段)

推荐阅读关于"高并发系统设计"看这篇就够了,阿里、百度、美团都在用Redis作为目前的主流NoSql数据库,不会是不可能的,在面试中也是非常高频的,一定不能在这个环节丢分,不管是学习,还是面试,以下知识点,都... 查看详情

架构实践架构师必知必会的5种业界主流的架构风格

 【架构实践】架构师必知必会的5种业界主流的架构风格目录 【架构实践】架构师必知必会的5种业界主流的架构风格 查看详情

架构实践架构师必知必会的5种业界主流的架构风格

 【架构实践】架构师必知必会的5种业界主流的架构风格目录 【架构实践】架构师必知必会的5种业界主流的架构风格 查看详情

10个必知必会的统计学问题

...?假设检验的内涵和步骤?这篇文章带你来看10个必知必会的统计学问题。正文来源:计量经济学1、问:自由度是什么?怎样确定?答:ÿ 查看详情

多线程必知必会的知识点

1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。2)... 查看详情

好奇?!elasticsearch25个必知必会的默认值

题记:技术交流群中有小伙伴提及:“es节点默认1000个分片的限制”?这引发了我对Elasticsearch默认值的关注。我一搜不要紧:聊天记录中涉及“默认”关键词的讨论接近400多处。这些默认值对于架构选型、开发实战、运维排查... 查看详情

elasticsearchelasticsearch25个必知必会的默认值

1.概述转载:https://elastic.blog.csdn.net/article/details/106464359题记:技术交流群中有小伙伴提及:“es节点默认1000个分片的限制”?这引发了我对Elasticsearch默认值的关注。我一搜不要紧:聊天记录中涉及“默认”关... 查看详情

❤️hadoop必知必会的基本知识❤️

🏃‍HDFS🏊‍HDFS的组成架构:这种架构主要由四个部分组成,分别为HDFSClient、NameNode、DataNode和SecondaryNameNode。下面我们分别介绍这四个组成部分。1)Client:就是客户端。 (1)文件切分。文件上... 查看详情

❤️hadoop必知必会的基本知识❤️

🏃‍HDFS🏊‍HDFS的组成架构:这种架构主要由四个部分组成,分别为HDFSClient、NameNode、DataNode和SecondaryNameNode。下面我们分别介绍这四个组成部分。1)Client:就是客户端。 (1)文件切分。文件上... 查看详情

elasticsearch必知必会的干货知识二:es索引操作技巧(代码片段)

该系列上一篇文章《Elasticsearch必知必会的干货知识一:ES索引文档的CRUD》讲了如何进行index的增删改查,本篇则侧重讲解说明如何对index进行创建、更改、迁移、查询配置信息等。仅创建索引:PUTindexPUT/index添加字段设置(mappings... 查看详情

数据结构和算法学编程必知必会的50个代码实现,你都会了吗?

❤️数组实现一个支持动态扩容的数组实现一个大小固定的有序数组,支持动态增删改操作实现两个有序数组合并为一个有序数组🧡链表实现单链表、循环链表、双向链表,支持增删操作实现单链表反转实现两个有... 查看详情

持续更新,建议收藏python必知必会的知识点,极大提升开发效率(代码片段)

问题本文主要介绍Python编程的一些必知必会的知识点,方便后续编程,提升效率。方法字符串转整数#字符串是普通整数a=int('1000')print(a)#1000#字符串是二进制b=int('1000',2)print(b)#8#整数转固定长度的二进制... 查看详情

大数据必知必会的-linux命令(代码片段)

用户的创建和删除命令用户创建和密码设置useradd用户名passwd用户名useradditheima#创建新用户itheimapasswditheima#设置用户itheima密码用户删除user-r用户名userdel-ritheima#删除用户itheima权限管理命令文件权限概述Linux操作系统是多任务多用... 查看详情

大数据必知必会的-linux命令(代码片段)

用户的创建和删除命令用户创建和密码设置useradd用户名passwd用户名useradditheima#创建新用户itheimapasswditheima#设置用户itheima密码用户删除user-r用户名userdel-ritheima#删除用户itheima权限管理命令文件权限概述Linux操作系统是多任务多用... 查看详情

zookeeper必知必会的知识点

ZooKeeper是什么?ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接... 查看详情

省时提效!5个必知必会的sql窗口函数!(代码片段)

SQL是数据世界中的通用语言,是作为数据科学人员必备技能。它之所以如此重要,是因为许多数据探索、数据操作、管道开发和仪表板创建都是通过SQL完成的。想要利用SQL高效处理数据任务,掌握一些窗口函数非常有... 查看详情