万万没想到,JVM内存区域的面试题也可以问的这么难?
二、Java内存区域
1、Java内存结构
内存结构
- 程序计数器
当前线程所执行字节码的行号指示器。若当前方法是native的,那么程序计数器的值就是undefined。
线程私有,Java内存区域中唯一一块不会发生OOM或StackOverflow的区域。
- 虚拟机栈
就是常说的Java栈,存放栈帧,栈帧里存放局部变量表等信息,方法执行到结束对应着一个栈帧的入栈到出栈。
线程私有,会发生StackOverflow。
- 本地方法栈
与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的。
线程私有,会发生StackOverflow。
- 堆
Java 虚拟机中内存最大的一块,几乎所有的对象实例都在这里分配内存。
是被所有线程共享的,会发生OOM。
- 方法区
也称非堆,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
是被所有线程共享的,会发生OOM。
- 运行时常量
是方法区的一部分,存常量(比如static final修饰的,比如String 一个字符串)和符号引用。
是被所有线程共享的,会发生OOM。
2、对象创建时堆内存分配算法
- 指针碰撞
前提要求堆内存的绝对工整的。
所有用过的内存放一边,没用过的放另一边,中间放一个分界点的指示器,当有对象新生时就已经知道大小了,指示器只需要像没用过的内存那边移动与对象等大小的内存区域即可。
指针碰撞
- 空闲列表
假设堆内存并不工整,那么空闲列表最合适。
JVM维护一个列表 ,记录哪些内存块是可用的,当对象创建时从列表中找到一块足够大的空间划分给新生对象,并将这块内存标记为已用内存。
空闲列表
3、对象在内存中的存储布局
分为三部分:
- 对象头
包含两部分:自身运行时数据和类型指针。
自身运行时数据包含:hashcode、gc分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等
对象指针就是对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例
- 实例数据
用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
- 对齐填充
JVM要求对象起始地址必须是8字节的整数倍(8字节对齐),所以不够8字节就由这部分来补充。
4、对象怎么定位
如下两种,具体用哪种有JVM来选择,hotspot虚拟机采取的直接指针方式来定位对象。
- 直接指针
栈上的引用直接指向堆中的对象。好处就是速度快。没额外开销。
- 句柄
Java堆中会单独划分出一块内存空间作为句柄池,这么一来栈上的引用存储的就是句柄地址,而不是真实对象地址,而句柄中包含了对象的实例数据等信息。好处就是即使对象在堆中的位置发生移动,栈上的引用也无需变化。因为中间有个句柄。
5、判断对象是否能被回收的算法
- 引用计数法
给对象添加一个引用计数器,每当有一个地方引用他的时候该计数器的值就+1,当引用失效的时候该计数器的值就-1;当计数器的值为0的时候,jvm判定此对象为垃圾对象。存在内存泄漏的bug,比如循环引用的时候,所以jvm虚拟机采取的是可达性分析法。
- 可达性分析法
有一些根节点GC Roots作为对象起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象为垃圾对象。
补充:哪些可作为GC Roots?
- 虚拟机栈中的引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(native方法)引用的对象
6、如何判断对象是否能被回收
该对象没有与GC Roots相连
该对象没有重写finalize()方法或finalize()已经被执行过则直接回收(第一次标记)、否则将对象加入到F-Queue队列中(优先级很低的队列)在这里finalize()方法被执行,之后进行第二次标记,如果对象仍然应该被GC则GC,否则移除队列。(在finalize方法中,对象很可能和其他 GC Roots中的某一个对象建立了关联,那就自救了,就不会被GC掉了,finalize方法只会被调用一次,且不推荐使用finalize方法)
7、Java堆内存组成部分
堆组成部分
堆大小 = 新生代 + 老年代。如果是Java8则没有Permanent Generation,Java8将此区域换成了Metaspace。
其中新生代(Young) 被分为 Eden和S0(from)和S1(to)。
默认情况下Edem : from : to = 8 : 1 : 1,此比例可以通过 –XX:SurvivorRatio 来设定
8、什么时候抛出StackOverflowError
方法运行的时候栈的深度超过了虚拟机容许的最大深度的时候,所以不推荐递归的原因之一也是因为这个,效率低,死归的话很容易就StackOverflowError了。
9、Java中会存在内存泄漏吗,请简单描述。
虽然Java会自动GC,但是使用不当的话还是存在内存泄漏的,比如ThreadLocal忘记remove的情况。(ThreadLocal篇幅过长,不适合放到这里,懂者自懂,不懂Google)
10、栈帧是什么?包含哪些东西
栈帧中存放的是局部变量、操作数栈、动态链接、方法出口等信息,栈帧中的局部变量表存放基本类型+对象引用+returnAddress,局部变量所需的内存空间在编译期间就完成分配了,因为基本类型和对象引用等都能确定占用多少slot,在运行期间也是无法改变这个大小的。
11、简述一个方法的执行流程
方法的执行到结束其实就是栈帧的入栈到出栈的过程,方法的局部变量会存到栈帧中的局部变量表里,递归的话会一直压栈压栈,执行完后进行出栈,所以效率较低,因为一直在压栈,栈是有深度的。
12、方法区会被回收吗
方法区回收价值很低,主要回收废弃的常量和无用的类。
如何判断无用的类:
该类所有实例都被回收(Java堆中没有该类的对象)
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类
13、一个对象包含多少个字节
会占用16个字节。比如
Object obj = new Object();
因为obj引用占用栈的4个字节,new出来的对象占用堆中的8个字节,4+8=12,但是对象要求都是8的倍数,所以对象的字节对齐(Padding)部分会补齐4个字节,也就是占用16个 字节。
再比如:
public class NewObj {
int count;
boolean flag;
Object obj;
}
NewObj obj = new NewObj();
这个对象大小为:空对象8字节+int类型4字节+boolean类型1字节+对象的引用4字节=17字节,需要8的倍数,所以字节对齐需要补充7个字节,也就是这段程序占用24字节。
14、为什么把堆栈分成两个
栈代表了处理逻辑,堆代表了存储数据,分开后逻辑更清晰,面向对象模块化思想。
栈是线程私有,堆是线程共享区,这样分开也节省了空间,比如多个栈中的地址指向同一块堆内存中的对象。
栈是运行时的需要,比如方法执行到结束,栈只能向上增长,因此会限制住栈存储内容的能力,而堆中的对象是可以根据需要动态增长的。
15、栈的起始点是哪
main函数,也是程序的起始点。
16、为什么基本类型不放在堆里
因为基本类型占用的空间一般都是1-8个字节(所需空间很少),而且因为是基本类型,所以不会出现动态增长的情况(长度是固定的),所以存到栈上是比较合适的。反而存到可动态增长的堆上意义不大。
17、Java参数传递是值传递还是引用传递
值传递。
基本类型作为参数被传递时肯定是值传递;引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。假设方法参数是个对象引用,当进入被调用方法的时候,被传递的这个引用的值会被程序解释到堆中的对象,这个时候才对应到真正的对象,若此时进行修改,修改的是引用对应的对象,而不是引用本身,也就是说修改的是堆中的数据,而不是栈中的引用。
18、为什么不推荐递归
因为递归一直在入栈入栈,短时间无法出栈,导致栈的压力会很大,栈也有深度的,容易爆掉,所以效率低下。
19、为什么参数大于2个要放到对象里
因为除了double和long类型占用局部变量表2个slot外,其他类型都占用1个slot大小,如果参数太多的话会导致这个栈帧变大,因为slot大,放个对象的引用上去的话只会占用1个slot,增加堆的压力减少栈的压力,堆自带GC,所以这点压力可以忽略。
20、常见笔试题
问题:输出结果是什么?
答案:aaa、aaa、abc
原因:其实也是值传递还是引用传递的问题。具体核心原因:main函数的str引用和zcd在栈上,而其对应的值在方法区或堆上。test1、test2、test3的参数也在栈上,这个空间和main上的不是同一块,是不同的栈帧。所以你修改test方法的数据对于main函数其实是无感知的。但是对象的引用的话修改的是堆内存中的对象属性值,所以有感知,那为什么test2输出的是aaa而不是abc呢?因为test2把堆中的对象都给换了,重新生成一个全新对象,对main上的引用来讲是看不到的,具体如下三幅图:
(1)aaa
题1
(2)aaa
题2
(3)abc
题3
public class TestChuandi {
public static void main(String[] args) {
String str = "aaa";
test1(str);
// aaa
System.out.println(str); Zhichuandi zcd = new Zhichuandi();
zcd.setName("aaa"); // aaa
test2(zcd);
System.out.println(zcd.getName()); // abc
test3(zcd);
System.out.println(zcd.getName());
} private static void test1(String s) {
s = "abc";
} private static void test2(Zhichuandi zcd) {
zcd = new Zhichuandi();
zcd.setName("abc");
} private static void test3(Zhichuandi zcd) {
zcd.setName("abc");
}
} class Zhichuandi {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
END
推荐好文
强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!
分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!
万万没想到,JVM内存区域的面试题也可以问的这么难?的更多相关文章
- 小白也能看懂的JVM内存区域
前言 最近在准备面试题刷到了JVM这块,作为一个小白,巩固知识点最好的方式就是亲手写出来并分享:相信我的理解,同样是小白的你,一定有很大的帮助.不信,请你往下看! JVM内存区域简介 如果有人问Jav ...
- JVM内存区域划分及垃圾回收
第一部分.闲扯+概述 近来在研读<深入理解java虚拟机>一书,读完之后做个小结,算是记录一下自己的学习所得,在成长的路上,只能死磕. 要理解JVM,就要先从其内存区域划分开始,知道其由几 ...
- JVM基础知识(1)-JVM内存区域与内存溢出
JVM基础知识(1)-JVM内存区域与内存溢出 0. 目录 什么是JVM 运行时数据区域 HotSpot虚拟机对象探秘 OutOfMemoryError异常 1. 什么是JVM 1.1. 什么是JVM ...
- JVM内存区域模型
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆” ,"perm", 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共 ...
- 初始jvm(一)---jvm内存区域与溢出
jvm内存区域与溢出 为什么学习jvm 木板原理,最短的一块板决定一个水的深度,当一个系统垃圾收集成为瓶颈的时候,那么就需要你对jvm的了解掌握. 当一个系统出现内存溢出,内存泄露的时候,因为你懂jv ...
- JVM内存区域详解
本文分为两部分:一是JVM内存区域的讲解:二是常见的内存溢出异常分析. 1.JVM内存区域 java虚拟机在执行java程序的过程中会把它管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途, ...
- Java虚拟机------JVM内存区域
JVM内存区域运行时数据区域分为两种: JVM内存区域 运行时数据区域分为两种: 线程隔离的数据区: 程序计数器 Java虚拟机栈 本地方法栈 所有线程程共享的数据区: Java堆 方法区 JVM 内 ...
- JVM 内存区域 (运行时数据区域)
JVM 内存区域 (运行时数据区域) 链接:https://www.jianshu.com/p/ec479baf4d06 运行时数据区域 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内 ...
- 走进JVM【二】理解JVM内存区域
引言 对于C++程序员,内存分配与回收的处理一直是令人头疼的问题.Java由于自身的自动内存管理机制,使得管理内存变得非常轻松,不容易出现内存泄漏,溢出的问题. 不容易不代表不会出现问题,一旦内存泄漏 ...
随机推荐
- css3动画帧
动画帧实现: css3使用steps来实现逐帧动画,动画过程中可能出现抖动,实乃精度偏差问题. 通常在动画里用到百分比单位时会出现抖动或位移现象,解决方法就是转换成具体的rem或px长度单位. 动画一 ...
- GC agent的安装和卸载
一.GC agent安装 下面介绍GC agent的push和pull两种安装方法 1.push(推送)安装GC agent方法 1).打开EMGC home page:https://even.or ...
- php项目从github自动pull到服务器
php项目github自动pull到服务器 项目名:web 一.自动触发 1.在服务器添加脚本文件:gitpull.sh #!/bin/sh cd /www/web git reset --hard ...
- 【JAVA基础&Python】静态/非静态代码块
/* * * static静态代码块: * 调用静态属性的时候 对应类里面的静态代码块就会被直接执行 * 注意: 只会执行一次,只能调用类内静态结构的(方法/属性) * 作用: 初始化类的属性 * * ...
- JVM的艺术—JAVA内存模型
*喜欢文章,动动手指点个赞 * 引言 亲爱读者你们好,关于jvm篇章的连载,前面三章讲了类加载器,本篇文章将进入jvm领域的另一个知识点,java内存模型.彻底的了解java内存模型,是有必要的.只要 ...
- k8s ingress - traefik
前面提到过 k8s 的 ingress 有 ingress-nginx,traefik,haproxy 等多种.今天来实践一下 tarefik. 闲言少叙,直接上代码. # cat traefik.y ...
- [游记]FCS&FJOI2018滚粗记
省冬连着省选,嗯这篇博客是省冬前就开的 省选是在情人节前一天- day0 中午早早的来了这边(找了个酒店到房间发现非常粉w 下午一个人去附中报到,然而-没有人带队签安全责任书好像不行-签到失败QAQ ...
- Netty源码解析 -- PoolSubpage实现原理
前面文章说了PoolChunk如何管理Normal内存块,本文分享PoolSubpage如何管理Small内存块. 源码分析基于Netty 4.1.52 内存管理算法 PoolSubpage负责管理S ...
- Java篇:Docker的介绍安装 和常用命令
文章目录 为什么 出现docker Docker的简介 容器(Container) 镜像(Image) 仓库(Repository) Docker的安装 查看容器 删除镜像 删除容器 部署应用 以my ...
- k8s之深入解剖Pod(二)
目录: Pod配置管理:ConfigMap 容器内获取Pod信息:Downward API Pod生命周期和重启策略 Pod健康检查 一.ConfigMap 将应用所需的配置信息与程序进行分离,可以使 ...