免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 23339 | 回复: 5

【话题讨论+送书福利】如何成为Java大神级程序员?虚拟机的知识你掌握了吗? [复制链接]

论坛徽章:
0
发表于 2021-09-15 13:51 |显示全部楼层
话题背景:

Java是一门流行多年的高级编程语言,与其相关的就业岗位很多,但是最近几年却出现了用人单位招不到合适的人,而大量Java程序员找不到工作的尴尬局面。究其根本原因是岗位对Java开发技术的要求高,不但要会应用,而且更要懂其内部的运行原理。对于想要深入研究Java技术的从业人员来说,虚拟机是绕不开的话题。近期机械工业出版社出版了《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》一书,是一本详细剖析工业级Java虚拟机HotSpot源码实现的书籍,是Java开发者探索Java底层原理,竞争高端职位不可多得的一本好书。本次活动便是以此为契机,和各位Java开发者就Java虚拟机的相关话题展开讨论,欢迎大家踊跃发言。


本次话题:

(1)请详细说说你所知道的Java类的生命周期。

(2)Java是如何对运行时数据区进行划分的?为什么要这么划分?

(3)详细说一下虚拟机为Java对象分配内存的过程,越详细越好。

(4)假设年轻代采用复制算法,老年代采用“标记-整理”算法管理虚拟机堆时,当线上发生YGC过于频繁、YGC的STW过长、FGC过于频繁、FGC的STW过长这几种情况时,请列举每个现象发生的可能原因,要有理有据。例如,YGC频繁,那么可能是年轻代采用了单线程的复制算法导致内存回收效率低下,而内存的分配速度大时就造成了年轻代的内存吃紧,又或者年轻代分配的内存过小而又不能动态扩容的情况下导致频繁YGC。



本期奖品:

最佳积极参与经验分享奖5名,奖励价值149元的《深入剖析Java虚拟机:源码剖析与实例详解(基础卷)》图书1本。

深入剖析Java虚拟机:源码剖析与实例详解(基础卷)
马智  著
书号:978-7-111-68989-8
印张:32(共512页)
定价:149.00元
上架建议:计算机/Java
深入剖析Java虚拟机立体封面.jpg


参与方式:

直接在该主题下回帖即可。
活动时间:2021年9月15日-10月15日


图书购买:

京东:https://item.jd.com/12926187.html
当当:http://product.dangdang.com/29291057.html


图书试读: 第1-2章.pdf (1.55 MB, 下载次数: 11)

论坛徽章:
58
2015七夕节徽章
日期:2015-08-24 11:17:25ChinaUnix专家徽章
日期:2015-07-20 09:19:30每周论坛发贴之星
日期:2015-07-20 09:19:42ChinaUnix元老
日期:2015-07-20 11:04:38荣誉版主
日期:2015-07-20 11:05:19巳蛇
日期:2015-07-20 11:05:26CU十二周年纪念徽章
日期:2015-07-20 11:05:27IT运维版块每日发帖之星
日期:2015-07-20 11:05:34操作系统版块每日发帖之星
日期:2015-07-20 11:05:36程序设计版块每日发帖之星
日期:2015-07-20 11:05:40数据库技术版块每日发帖之星
日期:2015-07-20 11:05:432015年辞旧岁徽章
日期:2015-07-20 11:05:44
发表于 2021-09-15 21:59 |显示全部楼层
沙发先坐上。

论坛徽章:
8
15-16赛季CBA联赛之青岛
日期:2017-05-25 14:27:3415-16赛季CBA联赛之深圳
日期:2017-07-19 09:39:23CU十四周年纪念徽章
日期:2017-08-29 16:08:0115-16赛季CBA联赛之佛山
日期:2017-08-30 19:12:5515-16赛季CBA联赛之山西
日期:2017-12-20 13:50:5519周年集字徽章-19
日期:2019-09-12 16:11:0719周年集字徽章-庆
日期:2019-09-12 16:13:3215-16赛季CBA联赛之北控
日期:2020-04-26 16:30:57
发表于 2021-09-17 10:00 |显示全部楼层
(1)请详细说说你所知道的Java类的生命周期。
一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,其中“连接”又可以分为验证、准备、解析三个小阶段

(2)Java是如何对运行时数据区进行划分的?为什么要这么划分?
Runtime area分区在不同VM上有明显的区别,在一般的Hotspot VM上,每个Thread都有自己的程序计数器和Stack,然后GC主要发生在Heap里,class信息和常量池保存在的Heap老生代;而Android的ART VM,每个Thread有自己的程序计数器和VM Stack以及native Stack,class信息和常量池保存在单独的method area

(3)详细说一下虚拟机为Java对象分配内存的过程,越详细越好。
恰当地说,本题就是问GC原理,新创建的对象都要被优先分配到新生代,过大的对象会直接加入老生代,新生代采用 标记-复制 算法,而老生代采用 标记-清除 算法,如果碎片过多,则采用 标记-整理 算法

(4)假设年轻代采用复制算法,老年代采用“标记-整理”算法管理虚拟机堆时,当线上发生YGC过于频繁、YGC的STW过长、FGC过于频繁、FGC的STW过长这几种情况时,请列举每个现象发生的可能原因,要有理有据。例如,YGC频繁,那么可能是年轻代采用了单线程的复制算法导致内存回收效率低下,而内存的分配速度大时就造成了年轻代的内存吃紧,又或者年轻代分配的内存过小而又不能动态扩容的情况下导致频繁YGC。
防止频繁GC,建议采用享元模式复用对象解决

论坛徽章:
0
发表于 2021-09-17 15:22 |显示全部楼层
(1)请详细说说你所知道的Java类的生命周期。
  • 加载:class字节码文件载入到虚拟机
  • 连接:

    • 验证一下这个类是否合法
    • 类的静态变量分配内存并设为jvm默认的初值

    • 常量池中的符号引用转换为直接引用
  • 初始化:如果一个类被直接引用,就会触发类的初始化
  • 使用:
  • 卸载

    • 该类所有的实例都已经被回收
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有任何地方被引用
(2)Java是如何对运行时数据区进行划分的?为什么要这么划分?
  • 元空间、堆、虚拟机栈、本地方法栈和程序计数器
  • 主要是为了方便垃圾收集器对对象进行管理
(3)详细说一下虚拟机为Java对象分配内存的过程,越详细越好。
  • 依据逃逸分析,如果未逃逸则把对象分配到VM Stack
  • 否则判断大对象,如果是则直接分配到堆上 Old Generation 老年代上
  • 否则判断是否可以在 TLAB(线程分配缓冲区)中分配,如果是在 TLAB中分配
  • 否则,在共享的Eden区分配。
(4)假设年轻代采用复制算法,老年代采用“标记-整理”算法管理虚拟机堆时,当线上发生YGC过于频繁、YGC的STW过长、FGC过于频繁、FGC的STW过长这几种情况时,请列举每个现象发生的可能原因,要有理有据。例如,YGC频繁,那么可能是年轻代采用了单线程的复制算法导致内存回收效率低下,而内存的分配速度大时就造成了年轻代的内存吃紧,又或者年轻代分配的内存过小而又不能动态扩容的情况下导致频繁YGC。
  • YGC过于频繁

    • Young/Eden 区过小
    • System.gc频繁调用
  • YGC的STW过长

    • CPU过高
    • 回收对象过多
  • FGC过于频繁

    • Young/Eden 区过小
    • System.gc频繁调用

    • 老年代的空间不足
    • 对象晋升年龄过小

    • 大的对象过多
    • 动态加载的类过多
  • FGC的STW过长

    • 同FGC过于频繁
    • 内存泄漏

论坛徽章:
32
CU大牛徽章
日期:2013-05-20 10:45:13每日论坛发贴之星
日期:2015-09-07 06:20:00每日论坛发贴之星
日期:2015-09-07 06:20:00数据库技术版块每日发帖之星
日期:2015-12-13 06:20:0015-16赛季CBA联赛之江苏
日期:2016-03-03 11:56:13IT运维版块每日发帖之星
日期:2016-03-06 06:20:00fulanqi
日期:2016-06-17 17:54:25IT运维版块每日发帖之星
日期:2016-07-23 06:20:0015-16赛季CBA联赛之佛山
日期:2016-08-11 18:06:41JAVA
日期:2016-10-25 16:09:072017金鸡报晓
日期:2017-01-10 15:13:292017金鸡报晓
日期:2017-02-08 10:33:21
发表于 2021-09-19 11:44 |显示全部楼层
本帖最后由 jieforest 于 2021-09-19 11:48 编辑

1)请详细说说你所知道的Java类的生命周期。
Java虚拟机为Java程序提供运行时环境,其中一项重要的任务就是管理类和对象的生命周期。
一个Java类完整的生命周期通常会经历加载、连接、初始化、使用、和卸载五个阶段,也存在加载或者连接之后没有被初始化就直接被使用的情况,可以用一个图来表示这个过程:

pic210.jpg

2)Java是如何对运行时数据区进行划分的?为什么要这么划分?
JVM虚拟机在运行Java应用程序的过程中,会将它管理的内存划分为若干个不同的数据区域。《Java虚拟机规范》这本书中描述了这种划分规定,将内存区域划分为:
1. 程序计数器(Program Counter Register):一块比较小的内存区域,可看做是当前线程执行字节码的行号指示器,以此实现指令的分支、循环、跳转、异常处理等功能。
2. 虚拟机栈(VM Stack):其栈元素是一种名为栈帧(Stack Frame)的数据结构,以便支持Java虚拟机进行方法调用和方法执行,当Java方法从开始调用到执行完成时,就对应着一个栈帧在虚拟机栈的入栈到出栈的过程。每一个栈帧都包含了:局部变量表(Local Variable Table)、操作数栈(Operand Stack)、动态链接(Dynamic Linking)、方法返回地址(Return Address),以及一些附加信息。
3. 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈相似,但此区域用于存储本地方法的局部变量表、本地方法的操作数栈等信息。与虚拟机栈最大的不同在于,虚拟机栈执行的是Java方法,而本地方法栈执行的是原生方法(Native Method),也就是C/C++方法。因为Java是高级编程语言,当访问底层OS或硬件时,需要通过JNI方式来调用原生方法来实现。
4. 方法区(Method Area):此区域存储已加载的类信息、常量、静态变量、即时编译后的代码等数据。其中类相关的信息有类名、访问修饰符、常量池、字段描述、方法描述等。方法区在逻辑上属于堆的一部分,但为了与堆进行区分,通常又称为“非堆”。方法区的数据是线程共享的。
5. 堆(Heap):堆是JVM管理的内存中最大的一块内存区域,几乎所有类/对象实例和数组的内存均从此处分配。之所以说“几乎”,是因为随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术导致了一些分配上的改变。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称作为GC堆(Garbage Collected Heap)。由于收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像磁盘那样。
以上五个区域。

3)详细说一下虚拟机为Java对象分配内存的过程,越详细越好。
很不错的问题,赞一个。
内存可分为两部分:
1. 栈内存(stack memory):有更快的访问效率且为连续分配
2. 堆内存(heap memory):比较慢的访问效率且为动态分配
JVM为Java对象分配内存是在堆内存区域处理的,而把栈内存区域用作函数调用等用途。
在Java语言中,JVM在启动时负责创建堆内存空间,且这个堆内存的尺寸是动态的的,可以根据程序的需求进行增加或减少。
我们还应该注意到Java也在堆外分配内存,这可用于:内部数据结构、方法/函数的调用、线程等。
在Java语言中,堆内存又分为两部分:
1. 年轻代(young generation)
2. 老年代(old generation)
在年轻代内存空间中,存储了最近创建的对象;而在老年代内存空间中,则存储长时间使用的对象。这种对象分类是为了更快和更有效的执行垃圾收集。
年轻代内存空间可进一步划分为保留区(keep area),它保留最近创建的对象。哪怕在垃圾收集时创建了大量的对象,也会将这些最近创建的对象存储到保留区。
当JVM为Java对象分配内存时,JVM会对Java对象进行识别:
1. 这是小对象
2. 这是很大的对象
这取决于对象所需的内存尺寸。我们可以使用工具包在Java中获取任何对象的大小。例如
```java
import java.lang.instrument.Instrumentation;
public class ObjectSizeFetcher {
    private static Instrumentation instrumentation;
    public static void premain(String args, Instrumentation inst) {
        instrumentation = inst;
    }
    public static long getObjectSize(Object o) {
        return instrumentation.getObjectSize(o);
    }
}
```
我们可以使用getObjectSize()方法来获取任意Java对象的尺寸。
判断Java对象的尺寸是大还是小,取决于多种因素,例如:
1. JVM版本
2. 堆大小
3. JVM垃圾回收策略(JVM设置)
4. 操作系统平台
小对象的大小通常在2KB~128KB,超过这个大小的对象就是大对象。小对象被放置在TLA(Thread Local Area,线程局部区域)节中。TLA是从堆中保留的空闲内存块。它被交给一个Java线程使用。如果空间可用,年轻代空间保留TLA,或者它将使用旧空间。
可以在JVM设置项中配置该尺寸:
1. -XXtiaSize
2. -XXlargeObjectLimit
大对象不适合TLA,因此直接在堆中分配。大对象的内存分配需要线程的同步。JVM使用了一组空闲内存块的缓存来:
1. 最小化同步需求
2. 加速内存分配
在Java语言中,当使用new()方法调用构造函数时,JVM会为对象分配内存。

4)假设年轻代采用复制算法,老年代采用“标记-整理”算法管理虚拟机堆时,当线上发生YGC过于频繁、YGC的STW过长、FGC过于频繁、FGC的STW过长这几种情况时,请列举每个现象发生的可能原因,要有理有据。例如,YGC频繁,那么可能是年轻代采用了单线程的复制算法导致内存回收效率低下,而内存的分配速度大时就造成了年轻代的内存吃紧,又或者年轻代分配的内存过小而又不能动态扩容的情况下导致频繁YGC。
1. YGC频繁:那么可能是年轻代采用了单线程的复制算法导致内存回收效率低下,而内存的分配速度大时就造成了年轻代的内存吃紧,又或者年轻代分配的内存过小而又不能动态扩容的情况下导致频繁YGC
2. YGC的STW过长:如果有大量的对象要复制,就会导致STW时间过长
3. FGC过于频繁:像Image对象存储了高清图像或者为数组分配了大量内存时,导致大对象进入了老年代,FGC可能就比较频繁
4. FGC的STW过长:堆中的碎片过多,或者FGC时OS有交换空间的操作或网络通信的操作活动

论坛徽章:
8
2017金鸡报晓
日期:2017-01-10 15:13:2915-16赛季CBA联赛之天津
日期:2019-06-20 14:25:4015-16赛季CBA联赛之天津
日期:2019-08-20 23:06:5319周年集字徽章-庆
日期:2019-08-27 13:24:4219周年集字徽章-19
日期:2019-09-06 18:55:5019周年集字徽章-年
日期:2019-09-06 18:55:5019周年集字徽章-周
日期:2019-09-20 17:18:2220周年集字徽章-CU
日期:2020-11-11 13:06:03
发表于 2021-09-19 16:45 |显示全部楼层
(1)请详细说说你所知道的Java类的生命周期。
Java类的生命周期类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。 其中加载、验证、准备、初始化、和卸载这5个阶段的顺序是确定的。

(2)Java是如何对运行时数据区进行划分的?为什么要这么划分?
运行时数据区主要分为以下几个部分:
·方法区
·虚拟机栈
·本地方法栈
·堆
·程序计数器
其中,按照线程在各个区域的数据是否共享划分为:
·线程共享部分:方法区、Java 堆以及运行时常量池(归属于方法区)
·线程私有部分:虚拟机栈、本地方法栈、程序计数器

JVM规范规定,JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途。以及创建和销毁的时间,有的区域随着虚拟机进程的启动就存在了,而有些区域则依赖用户线程的启动和结束而建立和销毁。JVM 规范对 JVM 定义了运行时统一的内存划分规范,统一了标准,类似于 JDBC 规范一样。其内存区域划分规范对于 JVM 的含义类似于我们 Java 中的接口,都是起到了规范的作用,JVM 是一台可以运行 Java 应用程序的抽象的计算机。

(3)详细说一下虚拟机为Java对象分配内存的过程,越详细越好。
虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,先执行相应的类加载过程。虚拟机为新生对象分配内存。对象所需内存大小在类加载完成后就可以确定,为对象分配内存等同于把一块确定大小的内存从Java堆中划分出来。
内存分配的方式有两种:① 指针碰撞: java堆如果规整,一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器; 分配内存只需向空闲那边移动指针空出与对象大小相等的空间;
② 空闲列表: 如果不规整,即用过的和空闲的内存相互交错;则虚拟机需要维护一个列表,记录哪些内存可用;分配内存时查表找到一个足够大的内存,并更新列表记录。
选择哪种分配方式是根据这个虚拟机所采用的垃圾收集器是否带有压缩整理功能决定的:如果虚拟机的虚拟器带压缩整理功能,则系统采用指针碰撞的内存分配算法;否则采用空闲列表的算法。
并发时,上面两种方式分配内存的操作都不是线程安全的,有两种解决方案:
① 同步处理
JVM采用CAS(Compare and Swap)机制加上失败重试的方式,保证更新操作的原子性;
CAS:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做;
② 本地线程分配缓冲区(TLAB)
把分配内存的动作按照线程划分在不同的空间中进行:每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB);哪个线程需要分配内存就从哪个线程的TLAB上分配;只有TLAB用完需要分配新的TLAB时,才需要同步处理。
JVM通过"-XX:+/-UseTLAB"指定是否使用TLAB。
内存分配完之后,虚拟机需要将分配到的内存空间都初始化为零值。如果用TLAB,则在TLAB分配时进行。这保证了程序中对象(及实例变量)不显式初始赋零值,程序也能访问到零值。
虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、 如何才能找到类的元数据信息、 对象的哈希码、 对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。
执行init方法,即按照程序员的意愿进行初始化。至此真正可用的对象才算完全被构造出来。

(4)假设年轻代采用复制算法,老年代采用“标记-整理”算法管理虚拟机堆时,当线上发生YGC过于频繁、YGC的STW过长、FGC过于频繁、FGC的STW过长这几种情况时,请列举每个现象发生的可能原因,要有理有据。例如,YGC频繁,那么可能是年轻代采用了单线程的复制算法导致内存回收效率低下,而内存的分配速度大时就造成了年轻代的内存吃紧,又或者年轻代分配的内存过小而又不能动态扩容的情况下导致频繁YGC。
导致FGC问题的可能原因包括:
1、大对象:系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。
2、内存泄漏:频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发FGC,最后导致OOM.
3、程序频繁生成一些长生命周期的对象,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发FGC. (即本文中的案例)
4、程序BUG导致动态生成了很多新类,使得 Metaspace 不断被占用,先引发FGC,最后导致OOM.
5、代码中显式调用了gc方法,包括自己的代码甚至框架中的代码。
6、JVM参数设置问题:包括总内存大小、新生代和老年代的大小、Eden区和S区的大小、元空间大小、垃圾回收算法等等。

导致YGC问题的可能原因包括:
1、对存活对象标注时间过长:比如重载了Object类的Finalize方法,导致标注Final Reference耗时过长;或者String.intern方法使用不当,导致YGC扫描StringTable时间过长。
2、长周期对象积累过多:比如本地缓存使用不当,积累了太多存活对象;或者锁竞争严重导致线程阻塞,局部变量的生命周期变长。
YGC问题其实比较难排查。相比FGC或者OOM,YGC的日志很简单,只知道新生代内存的变化和耗时,同时dump出来的堆内存必须要仔细排查才行。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

DTCC2021中国数据库技术大会

【数造未来】2021年10月18日-20日第十二届中国数据库技术大会
ITPUB、大会的会员您们好: 因目前国内疫情严峻,为响应北京市最新疫情防控要求,保障参会人员的健康和安全,组委会经协商决定:DTCC2021第十二届中国数据库技术大会延期至10月18日-20日(周一~周三)在北京国际会议中心举行,由此给各位带来的不便,敬请谅解!

大会官网
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP