JVM--八股

简介: JVM--八股

一  JVM 中的内存区域划分

JVM 其实是一个Java 进程,Java 进程会从操作系统这里申请一大块区域,给 java 代码使用.申请的这一大块区域会进一步划分,给出不同的用途,其中有最核心的三个区域:


1. 堆 :  new 出来的对象.(成员变量)


2. 栈 :  维护方法之间的调用关系.(局部变量)


3. 方法区/元数据区:  放的是类加载之后的类对象~~ (静态变量)


(Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。)

考题: 内置类型的变量是在栈上的, 引用类型的变量时在堆上


变量所在的位置和变量的类型是无关的. 例: Test t = new Test();


t 本身是一个引用类型,t 是一个局部变量,此时 t 是在栈上的;


new Test() 这个对象,对象的本体是在堆上的

内存区域划分图:

给个区域的作用:


1. 虚拟机栈,是给 java 代码使用的.


2. 本地方法栈, 是给 jvm 内部的本地方法使用的,(JVM 内部通过 C++ 代码实现的方法)


3. 程序计数器, 用途是纪录当前程序指定到哪个指令了, 简单的 long 类型的变量存了一个内存地址. 内存地址就是下一个要执行的 字节码 所在的地址


注: 堆和元数据区,在一个 jvm 进程中,只有一份. 栈(本地方法栈和虚拟机栈)和 程序计数器则是存在多份, 每个线程都有一份.

二  JVM 的类加载机制

类加载: 把 .class 文件,加载到内存, 得到类对象这样的过程.

类加载的五个步骤:

1. 加载

找到 .class 文件,并读取文件内容

双亲委派模型:

JVM 中, 加载类,需要用到一组特殊的模块,类加载器:


• BootStrap ClassLoader: 负责加载 Java 标准库中的类


• Extension ClassLoader:  负责加载一些非标准但是 Sun/Oracle 扩展的库的类


• Application ClassLoader:  负责加载项目中自己写的类以及第三方库中的类


当具体加载一个类的时候,需要先给定一个类的全限定名. 例: "java.lang.String"(字符串)


当加载时,首先是从Application ClassLoader 开始加载,但这个类并不能立刻开始搜索目录,首先需要它的父类去找(这三个类加载器是父子关系):

2. 验证

.class 文件有明确的数据格式(二进制文件)

3. 准备

给类对象分配内存空间

4. 解析

针对字符串常量进行初始化

字符串常量在 .class 文件中就存在了,但是由于它们还在文件中,只知道彼此之间的相对位置(偏移量),不知道在内存中的实际地址,只能使用特殊符号去占位.这时候的字符串常量就是符号引用


真正加载到内存中,就会把字符串填充到内存中的特定地址上,字符串常量之间的相对应位置还是一样的,但是这些字符串有了自己真正的内存地址,此时的字符串就是直接引用(java 中的普通的引用)

5. 初始化

针类对象进行初始化(初始化静态成员, 执行静态代码,类如果有父类,还要加载父类)

类加载这个动作,啥时候会触发?

并不是 jvm 一启动,就把所有的 .class 都加载了!! 整体是一个 "懒加载" 的策略(懒汉模式) 非必要,不加载

什么叫做"必要":

1. 创建了这个类的实例

2. 使用了这个类的静态方法/静态属性

3. 使用子类,会触发父类的加载

三  JVM 中的垃圾回收策略

JVM 中的内存有好几个区域,是释放那部分空间?

堆!!!(new 出来的对象)

程序计数器,就是一个单纯存地址的整数,不需要随着线程一起销毁,栈也是随着线程一起销毁,方法调用完毕,方法的局部变量自然随着出栈操作就销毁了,元数据区/方法区,存的类对象,很少会"卸载"

GC 中主要分成两个阶段:

1. 找, 确认谁是垃圾

java 中使用一个对象,只能通过引用来访问,如果一个对象,没有引用指向它,此时这个对象一定是无法被使用的(此时就是垃圾),如果一个对象不想用了,但是这个引用可能还指向着,此时就不是垃圾.


java中只是单纯通过引用没有指向这个操作,来判定垃圾的


具体来说,java 怎样知道一个对象是否有引用指向呢?


1. 引用计数: 给对象安排一个额外空间,保存一个整数,表示该对象有几个引用指向

(java 实际上没有使用这个方案, Python, PHP 采用了)

2. 可达性分析 :

可达性分析关键要点,就是需要有"起点"(gcroots):

1) 栈上局部变量(每个栈的每个局部变量,都是起点)


2) 常量池中引用的对象


3) 方法区中,静态成员引用的对象


可达性分析,就是从所有的 gcroots 的起点出发,看看该对象里又通过引用能访问哪些对象(类似于二叉树).顺藤摸瓜,把所有可以访问的对象都遍历一遍(遍历的同时把对象标记成"可达").剩下的自然是"不可达"


优点: 可达性分析,克服了引用计数的两个缺点,但是也有自己的缺点.


缺点:


1. 消耗更多的时间,因此某个对象成了垃圾,也不一定能第一时间发现,因为扫描的过程中,需要消耗时间        


2. 在进行可达性分析的时候,要顺藤摸瓜,一旦这个过程中,当前代码中的对象的引用关系发生变化了(当对象变成垃圾并没有被扫描出来),就麻烦了.因此,为了更准确的完成"顺藤摸瓜"这个过程,需要让其他的业务线程停止工作.就引出了 STW(stop the world)问题.

2. 释放,把垃圾对象的内存给释放掉

三种典型策略:

1.标记清除:

2. 复制算法

把整个内存够空间,分成成两段,一次只用一半

3. 标记整理

当2 和4 是垃圾,将3复制到2 中,将 5复制到 三的位置,然后释放内存.

优缺点

1. 能够解决内存碎片化问题

2. 搬运的开销太大

4. 分代回收

由于上述三种方法都不能很好的解决内存释放,因此实际上 JVM 的实现思路,是结合了上述几种的方法,针对不同的情况,使用不同的策略.

当我们new 一个对象时,年龄为0,每经过一轮扫描(可达性分析),没被标记成垃圾,年龄加一,针对不同的年龄对象采取了不同的回收策略

1. 新创建的对象,放到伊甸区. 当垃圾回收扫描到伊甸区之后, 绝大部分对象都会在第一轮 gc 中被干掉, 大部分对象时活不过一岁的(经验规律)


2.  如果伊甸区的对象,熬过第一轮 GC ,就会通过复制算法,拷贝到生存区,生存区分成两半(大小均等),一次只使用一半. 垃圾回收扫描伊甸区对象,也是发现垃圾就淘汰,不是垃圾的,通过复制算法,复制到生存区的另一半


3.  当这个对象在生存区,熬过若干轮 gc 之后,年龄增长到一定程度了,就会通过复制算法拷贝到老年代


4.  进入老年代的对象,年龄都很大了,再消亡的概率比前面新生代的对象小不少,针对老年代的 gc 的扫描频次就会降低很多. 如果老年代中发现某个对象是垃圾了,使用标记整理的方式清除


5. 特殊情况: 如果对象非常大,直接进入老年代,(大对象进行复制算法,成本比较高,而且大对象也不会很多)

相关文章
|
4月前
|
存储 监控 算法
JVM-2八股
JVM-2八股
|
4月前
|
存储 前端开发 算法
JVM八股
JVM 八股
|
6月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
551 55
|
17天前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
133 4
|
24天前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
7月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
532 6
|
10月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
992 166
|
12月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
1940 1
|
8月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
348 29
JVM简介—1.Java内存区域
|
8月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略