java虚拟机笔记一之内存介绍

今天简单记录下这段时间的笔记,是有关java虚拟机的知识。

小弟是第一次写技术博客,所以在自己的理解中肯定有很多地方会出现错误或不足之处,希望各位大神哥哥在看到我的理解后能够指点一下,小弟先谢谢各位了。接下来开始正题。

我们都知道在java是一门面向对象的语言,而我们都知道平时我们所用的方法中的局部变量是存放在栈内存中,对象实例是存放在堆内存中的。这两块内存区域也是很多人都同意的,剩下的内存区域有的人理解是方法区和常量区,有的人认为有方法区和数据区等等。那么针对这个问题,我们先来了解一下java虚拟机中到底分为哪些区域。

在介绍内存之前,我们先简单了解一下现在市面上比较著名的三大商用虚拟机:sun公司的HotSpot VM、BEA的JRockit VM和IBM的J9 VM。其中HotSpot和JRockit都被Oracle收购了,估计在未来,Oracle会将HotSpot和JRockit相结合,因为JRockit虚拟机的垃圾收集器和MissionControl服务套件等部分是java虚拟机中处于较领先的水平的。其中HotSpot和J9比较相似,它们是一款从服务器端到桌面应用再到嵌入式应用都考虑在内的虚拟机,而JRockit是一款专门为服务器硬件和服务器端应用的虚拟机。

在简单了解了三大虚拟机后,我们就选择sun的HotSpot进行研究吧。

1.程序计数器:研究过计算机组成原理的都知道CPU中也有个程序计数器,它是CPU的重要组成部分。而在java虚拟机内存中的程序计数器和CPU中的程序计数器相似,它记录着各个线程执行到的位置(指令的地址),可以这么理解:当线程之间轮流执行的时候,每条线程在切换到自己执行的时候,需要知道自己上一次执行到哪,这时候就用到程序计数器来指定下一条指令地址。所以它是线程私有的,其内存很小,甚至可忽略不计,并且它是java内存中唯一一个没有定义OutOfMemoryError的区域,因为它只是负责记录指令的地址而已。(另外,如果线程正在执行Native方法时,程序计数器的值就是空undefined,因为Native方法是由非java语言实现的,所以Native方法是放在原生平台去执行而不是java虚拟机)

2.java虚拟机栈:虚拟机栈也是线程私有的,它随线程而生,随线程死亡而结束。各个线程的各个方法在执行的时候,都会创建一个帧栈,帧栈中存储了局部变量表、操作站、动态链接、方法出口等信息。其中局部变量表就是我们平时所谓的"栈",它记录了基本数据类型和对象引用类型。随着方法的调用,帧栈入栈,方法结束,出栈,自动释放栈内存。如果想要达到栈溢出的效果,执行一个没有结束条件的递归方法即可达到。

3.本地方法栈:在HotSpot中,这片区域与java虚拟机栈划分在一起,它主要的作用就是为Native方法服务。它也是线程私有。

4.java堆:这片区域很重要,也是一片很大的空间,它是线程共享的。所有对象和数组都要在堆上分配空间。其中,java堆还可以按照对象的存活时间细分为年轻代和年老代。可以分代分配、分代回收空间,其中可以将年轻代再细分为:Eden区、From Survivor区和To Survivor区。具体的分配和回收算法在以后有机会补上,这里就不再赘述了。

5.方法区:方法区与堆相同,也是各个线程共享的内存区域,它用于存储被虚拟机加载的类的信息、静态变量、常量、方法等。其中方法区也被经常称为“永久代”。

6.运行时常量池:运行常量池是方法区的一部分,它用于存放编译器生成的各种字面量和符号引用(个人先理解为找到一个类的地址)。它的一个重要特征就是具有动态性,它可以在程序运行时将新的常量放入池中(例如String类的intern()方法)。

这里简单说下intern()方法,它是一个Native方法,它的作用就是:如果字符串常量池中已经包含了一个等于此String对象的字符串,则返回常量池中这个字符串的String对象,否则的话,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。比如,定义一个字符串str="abc",调用str.intern()方法,如果常量池中有"abc"(比如地址是0x666666),则返回这个常量池中的"abc",如果没有则在常量池中创建一个字符串"abc"(地址0x888888),并返回这个"abc"的引用。 这里有个例子,大家可以试试,也可以自己先猜一猜答案(提示:jdk1.6和jdk1.7中运行得到的结果会不相同)

<pre name="code" class="java"><span style="font-size:18px;">public class FirstJavaCode {

	public static void main(String[] args) {
		
		String str1 = new StringBuilder("计算机").append("软件").toString();
		String str2 = new StringBuilder("jav").append("a").toString();
		System.out.println(str1.intern() == str1);
		System.out.println(str2.intern() == str2);	
	}
}</span>
 

下面我给出答案了,首先是两张截图

第一张截图为jdk1.6下运行的结果:


第二张截图是jdk1.7运行的结果:


出现这种情况是因为在jdk1.6中,intern()方法会把首次遇到的字符串实例复制到常量池中,返回的也是常量池中这个字符串实例的引用,而由StringBuilder创建的字符串实例在堆空间上,所以==的结果必然是false(相当于深拷贝:拷贝整个对象并且增加一个引用指向这个拷贝的对象)。在jdk1.7中,intern()方法的不会再复制实例,而是只是在常量池中记录着首次出现的实例引用,因此intern()方法返回的引用和StringBuilder创建的对象的引用是同一个。对于str2来说,比较的是false是因为"java"这个字符串在常量池中已经有了其引用(java中的常量池中要是没有"java"这个常量那就怪了),所以,str2执行intern()方法后返回的引用为常量池中的"java"的引用,与StringBuilder在堆上创建对象的引用是同一引用,故不相等(jdk1.7中的intern()相当于浅拷贝:只是拷贝当前对象的引用)。另外,"计算机软件"常量池中没有,所以为首次出现,故调用intern()方法后使用==返回true。

今天是我第一次写个人技术博客,就先写到这里了,无论有没有人来看,在往后我会加油,坚持去写下去,不光为了自己能在写的过程中巩固知识,更重要的是能和那些技术大神进行交流。如果有什么错误或者不妥,请大家一定要指正!谢谢!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值