从一道面试题深入了解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虚拟机内存区域的作用。
从一道面试题深入了解java虚拟机内存结构的更多相关文章
- JVM基础系列第6讲:Java 虚拟机内存结构
看到这里,我相信大家对于一个 Java 源文件是如何变成字节码文件,以及字节码文件的含义已经非常清楚了.那么接下来就是让 Java 虚拟机运行字节码文件,从而得出我们最终想要的结果了.在这个过程中,J ...
- 深入剖析Java虚拟机内存结构
深入剖析Java虚拟机内存模型 JVM整体架构 JVM整体架构如下: 通过编写代码来分析整个内存区域 public class Math { public static final Integer C ...
- Java虚拟机内存模型及垃圾回收监控调优
Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存 ...
- Java程序猿从笨鸟到菜鸟之(九十二)深入java虚拟机(一)——java虚拟机底层结构具体解释
本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 在曾经的博客里面,我们介绍了在java领域中大部分的知识点,从最基础的java最基本的语法 ...
- 如何设置Java虚拟机内存以适应大程序的装载
Java虚拟机对于运行时的程序所占内存是有限制的,当我们的项目或者程序很大时,往往会照成内存溢出. 举个例子: public class SmallTest1 { public static void ...
- 打包apk java 虚拟机内存不足
解决方案:在android->sdk->build-tools-android-version 中有个 dx.bat dx.bat --dex 命令的dx.bat脚本有这样一句代码 REM ...
- Java虚拟机-内存tips
java虚拟机内存可以分为独占区和共享区. 独占区:虚拟内存栈.本地方法栈.程序计数器. 共享区:方法区.Java堆(用来存放对象实例). 程序计数器 比较小的内存空间,当前线程所执行的字节码的行号指 ...
- Java虚拟机内存溢出异常--《深入理解Java虚拟机》学习笔记及个人理解(三)
Java虚拟机内存溢出异常--<深入理解Java虚拟机>学习笔记及个人理解(三) 书上P39 1. 堆内存溢出 不断地创建对象, 而且保证创建的这些对象不会被回收即可(让GC Root可达 ...
- java虚拟机内存不足,“Could not create the Java Virtual Machine”问题解决方案
java虚拟机内存不足,"Could not create the Java Virtual Machine"问题解决方案 在运行java程序时,遇到问题"Could n ...
随机推荐
- alter add命令用来增加表的字段
alter add命令格式:alter table 表名 add字段 类型 其他; 例如,在表MyClass中添加了一个字段passtest,类型为int(4),默认值为0: mysql> al ...
- egret之移除带参数的监听事件
this.selectBtn.addEventListener(egret.TouchEvent.TOUCH_TAP, this.onClickNewIndo.bind(this,this.data) ...
- MySQL 5.7 的安装历程
mysql5.7零基础入门级的安装教程: 安装环境:Windows 10, 64 位(联想拯救者R720) 安装版本:mysql-5.7.25-winx64 一.下载 1.进入官网 首先,下载MySQ ...
- redis数据结构、持久化、缓存淘汰策略
Redis 单线程高性能,它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题.redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放 ...
- Git 忽略某些文件提交
在项目中有些配置文件不需要提交,但是有同学在后面开发中发现在.igonore文件中无论如何都无法忽略某些文件的提交.原因在这里: 已经维护起来的文件,即使加上了gitignore,也无济于事.---- ...
- 简单粗暴详细讲解javascript实现函数柯里化与反柯里化
函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第 ...
- 一起来读Netty In Action之netty的组件和设计(二)
在上一篇博客中,我们给出了java高性能网络编程的技术基础,也简单的介绍了netty的核心构件,在这一篇博客中,我们将更加详细的研究netty的各个组件,并且密切关注它们是如何通过协作来支撑这些体系结 ...
- Go语言基础之指针
区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针. 要搞明白Go语言中的指针需要先知道3个概念:指针地址.指针类型和指针取值. Go语言中的指针 Go语言中的函数传参都是值拷贝 ...
- java架构之路-(11)JVM的对象和堆
上次博客,我们说了jvm运行时的内存模型,堆,栈,程序计数器,元空间和本地方法栈.我们主要说了堆和栈,栈的流程大致也说了一遍,同时我们知道堆是用来存对象的,分别年轻代和老年代.但是具体的堆是怎么来存放 ...
- Spring Boot跨域解决方案
一.什么是跨域 为保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,这称之为同源策略,如果一个请求地址里的协议.域名.端口号都相同,就属于同源.依据浏览器同源策略,非同源脚 ...