从一道面试题深入了解java虚拟机内存结构

记得刚大学毕业时,为了应付面试,疯狂的在网上刷JAVA的面试题,很多都靠死记硬背。其中有道面试题,给我的印象非常之深刻,有个大厂的面试官,顺着这道题目,一直往下问,问到java虚拟机的知识,最后把我给问住了。
我当时的表情是这样的:

后来我有机会面试别人了,也按照他的思路出面试题,很多已经工作了2年的程序员,结果也和我当年一样,都败在java虚拟机知识上。

我们先看面试题:

String str1 = "hello Alunbar";
String str2 = new String(str1);

会创建几个对象?

网上给出的解释是创建2个对象,str1对象在常量池中,str2对象在堆中。

下面是我和面试官的对话。
面试官:上面的代码创建了几个对象?
我:2个。
面试官:为什么是2个呢?
我:str1对象在常量池中,str2对象在堆中。用“=”等号创建String对象时,会先从字符串常量池中查找是否已经存在字符串对象,存在就直接返回引用地址,否则创建字符串对象并返回引用地址。

面试官:为什么会在常量池中创建字符串对象?
我:。。。我思考了半分钟,尴尬的回答不知道。
面试官:说说jvm虚拟机的内存结构。
我:。。。我再次面露难色,场面一度非常尴尬。

这次面试结束之后,我就回去疯狂查找资料,了解jvm虚拟机的相关知识。

这也是我的第一次面试,给我的印象非常之深刻。

下面我们来说说面试官的两个问题。
1、为什么会在常量池中创建字符串对象。
2、java虚拟机的内存结构。

先来看第一个问题。
为什么会在常量池中创建字符串对象?

字符串在所有编程语言中都是最常用的类型,其他的数据类型都可以转换为字符串类型,像int、long等基本数据类型和String都是可以互相转换的。为了提高字符串的使用效率,jvm虚拟机中特别开辟了一个常量池的内存空间,用于存储基本数据类型的对象,常量池中的对象是可以相互共享的,当然也包括了String。

我们一般将储存字符串的常量池成为字符串常量池。字符串常量池中会存在很多已经创建好的字符串对象,由于String类是用final修饰的,它的值一经创建就不可改变,因此我们不用担心String对象共享而带来程序的混乱。

我们来看一段的代码:

String s1 = "Hello";
String s2 = "Hello";

这段代码只创建一个对象,s1和s2是同一个对象。根据上面的解读,java String s1 = "Hello"这行代码会先在字符串常量池查找Hello对象,没有发现,然后创建Hello对象并将引用返回给s1。java String s2 = "Hello"这行代码,也先去字符串常量池中查找Hello对象,发现已经存在,则直接返回给s2。因此s1和s2是同一个对象。

接着说说使用new创建字符串对象。
通过new创建字符串对象,会在堆中开辟一块新的内存空间,存储String字符串对象,因此使用new方式都会生成新的字符串对象,不管字符串的内容是否一致,使用new创建字符串时存在堆中,堆中的对象会被回收,而使用“=”创建字符串对象,是存放在常量池中,不会被回收,因此建议使用“=”的方式创建字符串对象,避免不必要的java对象创建和销毁的开销。

我们来看下面的创建字符串对象时的内存结构图:

s1和s2是通过“=”创建的字符串对象,它们的内存地址都一样,s3是使用new方式创建的字符串对象,s3和s1、s2的内存地址不一样。

现在接着看第二个问题。

java虚拟机的内存结构
虚拟机内存结构是一个很复杂的问题,这里只能讲一个大概,主要讲各个内存区域的作用。

java虚拟机由类加载器、运行时数据区和执行引擎构成。如下图所示:

平时我们说的java虚拟机内存结构,就是讲运行时数据区。

java虚拟机在执行java程序时,会将内存分为几个区域:程序计数器、方法区、虚拟机栈、本地方法栈、堆。

其中,方法区和堆是线程共享,程序计数器、虚拟机栈、本地方法栈时线程不共享。

1、程序计数器
只要学过汇编语言,对这个程序计数器都好理解,就是记录下一条将要执行的字节码指令。

通过操作系统知识我们知道启动一个程序时,就会创建一个进程,因此在执行java程序时,就会创建一个进程,java虚拟机就是一个进程。

一个进程中由多个线程组成,在任何一个时刻,java虚拟机只能执行一条线程中的指令。

java虚拟机通过读取某一个线程中的程序计数器决定该线程需要执行哪个基础功能,例如循环、读取数据库、跳转、异常处理、线程恢复等。

因此每个线程的程序计数器是相互独立,互不影响的。

2、java虚拟机栈
就是我们常说的java栈,在执行方法时,会在java栈中创建一个栈帧,用于存储局部变量表、操作数栈、方法出口等信息。

局部变量表中又会存放执行方法需要的boolean、char等各种基本数据类型,对象引用等。局部变量表大小在代码编译期间就已经确定。java栈也是线程私有。

创建线程时同步创建java栈,线程结束,java栈也同时销毁,释放占用的内存。

3、本地方法栈
和java虚拟机栈功能类似,有的虚拟机会将java虚拟机栈和本地方法栈合并。本地方法栈主要为虚拟机执行Native方法提供服务。

4、java堆
虚拟机中最大的一块内存区域,虚拟机启动时创建,主要用于存放对象实例,这块内存区域由所有线程共享。这个区域内的对象,可以被所有的线程访问。

这个区域也是java虚拟机重点管理的对象,当这块区域中的对象没有被引用,达到回收标准时,就会被java垃圾收集器回收,释放占用的内容空间。

java堆分为新生代和老年代,新生代又分为Eden空间、From Survivor空间和To Survivor空间。

使用new操作创建对象时,就会在这个区域开辟一块内存用于存储对象。

上面提到的java String str1 = new String("Hello")创建字符串,就会在java堆中开辟一块内存用于存储str1对象。

5、方法区
方法区主要存储被虚拟机加载的类信息、常量、静态变量等数据,我们也将这个内存区域称为永久代,这个区域不会进行内存回收。
方法区和java堆一样,所有线程共享。

方法区中包含一个运行时常量池,上面提到的java String str = "Hello"创建字符串,就是在运行时常量池中创建“Hello”对象。

小结:
1、两种创建字符串对象的差异。
2、java虚拟机内存区域的作用。

原文地址:https://www.cnblogs.com/airnew/p/11614031.html

时间: 2024-08-01 06:38:35

从一道面试题深入了解java虚拟机内存结构的相关文章

JVM——java虚拟机内存结构简介

JAVA虚拟机内存结构示意图 各区域介绍 程序计数器 程序计数器,用来记录每一个线程正在执行的指令地址.每一个线程有一个线程计数器,因此该区域是"线程私有"的内存,生命周期与线程相同.如果线程执行的是native本地方法,则程序计数器为空. 虚拟机栈区 每当线程执行一个方法时,虚拟机就会在这个区域创建一个栈帧,用于存储局部变量.动态链接.方法出口等信息.该区域也是"线程私有"的内存. 本地方法栈 本地方法栈主要是用来执行Native方法服务的 Java堆 所有线程共

Java虚拟机内存结构

起源 1960年Lisp语言: 第一门真正使用内存动态分配和垃圾回收的语言. 运行时数据区域 1.程序计数器 Program Counter Register 1. 当前线程所执行的字节码的行号指示器. 2. 字节码解释器的工作是通过改变这个计数器的值去选取下一条需要执行的字节码指令. 3. 每条线程都一个独立的程序计数器. 4. 如果执行java方法,该位置记录正在执行的字节码指令地址,如果是native方法,该位置为空. 该部分是唯一不会OutOfMemory的部分. 2.Java虚拟机栈

java虚拟机---内存

java虚拟机---内存 Java虚拟机,即JVM,负责运行java程序,每个java程序都运行在一个具体jvm实例上.Java虚拟机的体系架构分为:类装载子系统.运行时数据区.执行引擎.类装载子系统即负责加载.验证.解析.初始化java类的系统:Java虚拟机在运行一个程序时需要储存很多数据,如类装载信息.创建的实例对象.方法调用的参数.局部变量.中间值等,虚拟机把这些信息都储存在"运行时数据区"里,即这里讲的JVM内存:执行引擎则是以字节码形式的class文件为输入,运行程序输出计

如何设置Java虚拟机内存以适应大程序的装载

Java虚拟机对于运行时的程序所占内存是有限制的,当我们的项目或者程序很大时,往往会照成内存溢出. 举个例子: public class SmallTest1 { public static void main(String[] args) { byte[] array = new byte[1024*1024*500]; } } 当定义这样一个500MB的数组时,就会造成JVM内存溢出: 而Java虚拟机默认的程序运行能得到的内存大小是随系统的,由Java的api体系结构中,点击Java: 后

Java虚拟机内存分配详解

简介 了解Java虚拟机内存分布的好处 1.了解Java内存管理的细节,有助于程序员编写出性能更好的程序.比如,在新的线程创建时,JVM会为每个线程创建一个专属的栈 (stack),其栈是先进后出的数据结构,这种方式的特点,让程序员编程时,必须特别注意递归方法要尽量少使用,另外栈的大小也有一定的限制,如果过多 的递归,容易导致stack overflow. 2.了解Java内存管理的细节,一旦内存管理出现问题,有助于找到问题的根本原因所在. 3.了解Java内存管理的内幕,有助于优化JVM,从而

java虚拟机底层结构详解[转]

本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 在以前的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本语法到SSH框架.这里面应该包含了在java领域里面的大部分内容了吧.但是,那些知识点是让我们从一个应用的层面上了解了java,java程序真正底层的运行机制和一些底层虚拟机的工作我们还不了解,虽然这些内容在我们真正的开发中几乎用不到这些底层的东西,但对于我们对java的理解会有比较大的帮助.尤其也对以后

Java虚拟机底层结构详解

Java虚拟机 Java虚拟机(Java Virtual Machine) 简称JVM Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现.Java虚拟机有自己想象中的硬件,如处理器.堆栈.寄存器等,还具有相应的指令系统.下面我们就来看一下这几部分比较重要的java虚拟机的结构: 1)JVM寄存器 所有的CPU均包含用于保存系统状态和处理器所需信息的寄存器组.如果虚拟机定义义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行访问,这有利于提高运行速度.然而,如果虚拟机中

Java虚拟机内存管理机制

自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则是依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范 第2版>规定,运行时数据区包括: 1.程序计数器 一块较小的内存空间,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它.当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令.每条

从一道面试题(死循环里分配内存)阐述Linux的内存管理

题目: int cnt = 0; while(1) { ++cnt; ptr = (char *)malloc(1024*1024*128); if(ptr == NULL) { printf("%s\n", "is null"); break; } } printf("%d\n", cnt); 这个程序会有怎样的输出呢? 结果在Linux32位机是 is null 3057 为嘛是3057?? 因为用户态虚拟内存地址空间是3G. 3057M 大