二、Java内存区域

1、Java内存结构

万万没想到,JVM内存区域的面试题也可以问的这么难?

内存结构

  • 程序计数器

当前线程所执行字节码的行号指示器。若当前方法是native的,那么程序计数器的值就是undefined。

线程私有,Java内存区域中唯一一块不会发生OOM或StackOverflow的区域。

  • 虚拟机栈

就是常说的Java栈,存放栈帧,栈帧里存放局部变量表等信息,方法执行到结束对应着一个栈帧的入栈到出栈。

线程私有,会发生StackOverflow。

  • 本地方法栈

与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的。

线程私有,会发生StackOverflow。

Java 虚拟机中内存最大的一块,几乎所有的对象实例都在这里分配内存。

是被所有线程共享的,会发生OOM。

  • 方法区

也称非堆,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

是被所有线程共享的,会发生OOM。

  • 运行时常量

是方法区的一部分,存常量(比如static final修饰的,比如String 一个字符串)和符号引用。

是被所有线程共享的,会发生OOM。

2、对象创建时堆内存分配算法

  • 指针碰撞

前提要求堆内存的绝对工整的。

所有用过的内存放一边,没用过的放另一边,中间放一个分界点的指示器,当有对象新生时就已经知道大小了,指示器只需要像没用过的内存那边移动与对象等大小的内存区域即可。

万万没想到,JVM内存区域的面试题也可以问的这么难?

指针碰撞

  • 空闲列表

假设堆内存并不工整,那么空闲列表最合适。

JVM维护一个列表 ,记录哪些内存块是可用的,当对象创建时从列表中找到一块足够大的空间划分给新生对象,并将这块内存标记为已用内存。

万万没想到,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堆内存组成部分

万万没想到,JVM内存区域的面试题也可以问的这么难?

堆组成部分

堆大小 = 新生代 + 老年代。如果是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

万万没想到,JVM内存区域的面试题也可以问的这么难?

题1

(2)aaa

万万没想到,JVM内存区域的面试题也可以问的这么难?

题2

(3)abc

万万没想到,JVM内存区域的面试题也可以问的这么难?

题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内存区域的面试题也可以问的这么难?的更多相关文章

  1. Java 使用拦截器无限转发/重定向无限循环/重定向次数过多报错(StackOverflowError) 解决方案

    说明:当使用拦截器出现“请求转发”无限循环或者“重定向”次数过多这种问题的时候,一般都是 拦截器 设置错了情况一:请求转发时没有配置排除拦截路径,就是说你访问的路径都拦截了,导致一直转发,从而产生java.lang.StackOverflowError错误情况二:重定向时没有配置排除拦截路径,会导致......

  2. Java并发包源码学习系列:阻塞队列实现之ArrayBlockingQueue源码解析

    目录ArrayBlockingQueue概述类图结构及重要字段构造器出队和入队操作入队enqueue出队dequeue阻塞式操作E take() 阻塞式获取void put(E e) 阻塞式插入E poll(timeout, unit) 阻塞式超时获取boolean offer(e, timeout......

  3. java自定义注解验证手机格式的实现示例

    1、@Valid与@Validated的区别1.1 基本区别@Valid:Hibernate validation校验机制@Validated:Spring Validator校验机制,这个也是最常用的@Validation只是对@Valid进行了二次封装,在使用上并没有太大区别,但在分组、注解位置......

  4. Java序列化

    1.什么是序列化Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程:序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象......

  5. java编译命令基础知识点

    我们在对计算机下达指令时,人类的语言它是不能够明白,需要通过编译的时候翻译成计算机能听懂的语言。编译过程中会调用javac命令,这点大家可能接触的不多,毕竟是是计算机程序内部运行时的操作。下面我们就编译的概念、命令带来讲解,然后分享一个编译实例给大家练习。1.编译概念通过流程图可以看出其实java的......

  6. Ajax实现局部刷新的方法实例

    前言 最近复习了一下jQuery的一些内容,特此整理一下一些能用的得到的知识点,以前才学jQuery的时候压根就没有注意到那么多的细节,另外最近一直都在整理前端的一些工作中学到的小经验,大概还会有十篇左右的内容,就会慢慢开始整理后端,框架,以及数据库的一些小知识点 一、 Ajax是什么? 概念: A......

  7. 使用Groovy构建DSL

    DSL(Domain Specific Language)是针对某一领域,具有受限表达性的一种计算机程序设计语言。常用于聚焦指定的领域或问题,这就要求 DSL 具备强大的表现力,同时在使用起来要简单。由于其使用简单的特性,DSL 通常不会像 Java,C++等语言将其应用于一般性的编程任务。对于 G......

  8. Java 给Word不同页面设置不同背景

    Word文档中,可直接通过【设计】-【页面颜色】页面颜色,通过Java代码可参考如下设置方法:1. 设置单一颜色背景doc.getBackground().setType(BackgroundType.Color);doc.getBackground().setColor(Color.PINK);2......

  9. java中“==“和equals()的区别详解

    今天我们探讨一下Java中"=="与equals()的区别==:关系运算符在基本数据类型中比较两个值的内容是否相等在引用类型型中比较的是两个对象的地址是否相等equals()是Object类中的方法1.基本数据类型无法使用equals()方法2.在引用类型中若是没有重写Objec......

  10. JVM系列(三):JVM创建过程解析

    上两篇中梳理了整个java启动过程中,jvm大致是如何运行的。即厘清了我们认为的jvm的启动过程。但那里面仅为一些大致的东西,比如参数解析,验证,dll加载等等。把最核心的loadJavaVM()交给了一个dll或者so库。也就是真正的jvm我们并没有接触到,我们仅看了一个包装者或者是上层应用的实现......

随机推荐

  1. python用分数表示矩阵的方法实例

    前言在机器学习中,我们会经常和矩阵打交道。在矩阵的运算中,python默认的输出是浮点数,但是如果我们想要矩阵的元素以分数的形式显示,可以通过添加一行代码来实现。1、函数及参数解释set_printoptions()——控制输出方式formatter——通用格式化输出Fraction(x).limi......

  2. Android 中 WebView 的基本用法详解

    加载 URL (网络或者本地 assets 文件夹下的 html 文件)加载 html 代码Native 和 JavaScript 相互调用加载网络 URLwebview.loadUrl(https://www.baidu.com/);加载 assets 下的 html 文件webview.load......

  3. python基于爬虫+django,打造个性化API接口

    简述今天也是同事在做微信小程序的开发,需要音乐接口的测试,可是用网易云的开放接口比较麻烦,也不能进行测试,这里也是和我说了一下,所以就用爬虫写了个简单网易云歌曲URL的爬虫,把数据存入mysql数据库,再利用django封装装了一个简单的API接口,给同事测试使用。原理创建django项目,做好基础......

  4. Perl中常见符号与操作

    注释:1.单行: #print2.多行:=pod …. =cut查询perl相关:Perldoc perlvar($a,$$,$”...)Perldoc perfunc(sort…)Perldoc perl 查看perldoc文章列表Perldoc –q 正则表达式Perldoc –f 函数名Per......

  5. Java 执行过程中的内存模型

    一、前言本文的主要工作:尝试以时间顺序追踪一遍 Java 执行的整个过程,以及展示 JVM 中内存模型的相应变化。本文的主要目的:希望能够通过 Java 执行过程的冰山一角,增进对编程语言工作原理的理解。以下面这段代码为例,追踪它的执行过程:public class Car {private int......

  6. 关于C++中构造函数的常见疑问

    基本概念我们已经知道在定义一个对象时,该对象会根据你传入的参数来调用类中对应的构造函数。同时,在释放这个对象时,会调用类中的析构函数。其中,构造函数有三种,分别是默认构造函数,有参构造函数和拷贝构造函数。在类中,如果我们没有自行定义任何的构造函数,编译器会为我们提供两种构造函数(默认构造函数和拷贝构......

  7. .NET并发编程-函数式编程

    本系列学习在.NET中的并发并行编程模式,实战技巧函数式编程和面向过程编程POP(procedure oriented Programming)面向对象编程OOP(object oriented programming)一样也是一种编程思维。函数式编程FP(functional programmin......

  8. java DelayQueue的原理浅析

    在对DelayQueue延迟功能的使用上,很多人不能后完全理解延迟的一些功能使用,这里我们深入来挖掘一下DelayQueue的原理。下面将从构造方法、接口、继承体系三个方面进行分析,需要注意的是,相较于其它的阻塞队列,DelayQueue因为延迟的功能多了接口的使用,一起来看具体内容。1.构造方法p......

  9. JavaScript—深入理解函数

    当程序在调用某个函数时,做了以下的工作:准备执行环境,初始函数作用域链和arguments参数对象。函数概述函数的声明语句function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传 入函数的参数。函数体放在大括号里面。function hel......

  10. MYSQL数据库操作语句

    1.创建数据库CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification: [DEFAULT] CHARACTE......