获取java中String类型java判断对象类型的内存地址值

java 如何获得java对象的内存地址_百度知道
java 如何获得java对象的内存地址
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
以道承诺未就业全额退款
以道教育作为专业的IT培训教育公司,目前主要进行java大数据、云计算、人工智能、web全栈培训服务,提出“就业是检验真理的唯一标准”的教育理念,以道承诺未就业全额退还学费。
Java不允许获得内存地址!用ToString获得的只是其对象编号。Java语言设计就是不允许直接管理内存
为你推荐:
其他类似问题
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。java内存分配和String类型的深度解析 - ImportNew
| 标签: ,
在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题。下面是本文将要涉及到的一些问题,如果读者对这些问题都了如指掌,则可忽略此文。
1、java内存具体指哪块内存?这块内存区域为什么要进行划分?是如何划分的?划分之后每块区域的作用是什么?如何设置各个区域的大小?
2、String类型在执行连接操作时,效率为什么会比StringBuffer或者StringBuilder低?StringBuffer和StringBuilder有什么联系和区别?
3、java中常量是指什么?String s = “s” 和 String s = new String(“s”) 有什么不一样?
本文经多方资料的收集整理和归纳,最终撰写成文,如果有错误之处,请多多指教!
二、java内存分配
1、JVM简介
Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境,它是Java 最具吸引力的特性之一。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
一个运行时的Java虚拟机实例的天职是:负责运行一个java程序。当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。如果同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例。每个Java程序都运行于它自己的Java虚拟机实例中。
如下图所示,JVM的体系结构包含几个主要的子系统和内存区:
垃圾回收器(Garbage Collection):负责回收堆内存(Heap)中没有被使用的对象,即这些对象已经没有被引用了。
类装载子系统(Classloader Sub-System):除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。
执行引擎(Execution Engine):负责执行那些包含在被装载类的方法中的指令。
运行时数据区(Java Memory Allocation Area):又叫虚拟机内存或者Java内存,虚拟机运行时需要从整个计算机内存划分一块内存区域存储许多东西。例如:字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数,返回值、局部变量等等。
2、java内存分区
从上节知道,运行时数据区即是java内存,而且数据区要存储的东西比较多,如果不对这块内存区域进行划分管理,会显得比较杂乱无章。程序喜欢有规律的东西,最讨厌杂乱无章的东西。 根据存储数据的不同,java内存通常被划分为5个区域:程序计数器(Program Count Register)、本地方法栈(Native Stack)、方法区(Methon Area)、栈(Stack)、堆(Heap)。
程序计数器(Program Count Register):又叫程序寄存器。JVM支持多个线程同时运行,当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)。如果线程正在执行的是一个Java方法(非native),那么PC寄存器的值将总是指向下一条将被执行的指令,如果方法是 native的,程序计数器寄存器的值不会被定义。 JVM的程序计数器寄存器的宽度足够保证可以持有一个返回地址或者native的指针。
栈(Stack):又叫堆栈。JVM为每个新创建的线程都分配一个栈。也就是说,对于一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。我们知道,某个线程正在执行的方法称为此线程的当前方法。我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧,这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算过程和其他数据。从Java的这种分配机制来看,堆栈又可以这样理解:栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。其相关设置参数:
-Xss –设置方法栈的最大值
本地方法栈(Native Stack):存储本地方方法的调用状态。
方法区(Method Area):当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息,然后把这些类型信息(包括类信息、常量、静态变量等)放到方法区中,该内存区域被所有线程共享,如下图所示。本地方法区存在一块特殊的内存区域,叫常量池(Constant Pool),这块内存将与String类型的分析密切相关。
堆(Heap):Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。在此区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存,但是这个对象的引用却是在栈(Stack)中分配。因此,执行String s = new String(“s”)时,需要从两个地方分配内存:在堆中为String对象分配内存,在栈中为引用(这个堆对象的内存地址,即指针)分配内存,如下图所示。
JAVA虚拟机有一条在堆中分配新对象的指令,却没有释放内存的指令,正如你无法用Java代码区明确释放一个对象一样。虚拟机自己负责决定如何以及何时释放不再被运行的程序引用的对象所占据的内存,通常,虚拟机把这个任务交给垃圾收集器(Garbage Collection)。其相关设置参数:
-Xms — 设置堆内存初始大小
-Xmx — 设置堆内存最大值
-XX:MaxTenuringThreshold — 设置对象在新生代中存活的次数
-XX:PretenureSizeThreshold — 设置超过指定大小的大对象直接分配在旧生代中
Java堆是垃圾收集器管理的主要区域,因此又称为“GC 堆”(Garbage Collectioned Heap)。现在的垃圾收集器基本都是采用的分代收集算法,所以Java堆还可以细分为:新生代(Young Generation)和老年代(Old Generation),如下图所示。分代收集算法的思想:第一种说法,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍,以便让出更多的系统资源供应用系统使用;另一种说法,在分配对象遇到内存不足时,先对新生代进行GC(Young GC);当新生代GC之后仍无法满足内存空间分配需求时, 才会对整个堆空间以及方法区进行GC(Full GC)。
在这里可能会有读者表示疑问:记得还有一个什么永久代(Permanent Generation)的啊,难道它不属于Java堆?亲,你答对了!其实传说中的永久代就是上面所说的方法区,存放的都是jvm初始化时加载器加载的一些类型信息(包括类信息、常量、静态变量等),这些信息的生存周期比较长,GC不会在主程序运行期对PermGen Space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen Space错误。其相关设置参数:
-XX:PermSize –设置Perm区的初始大小
-XX:MaxPermSize –设置Perm区的最大值
新生代(Young Generation)又分为:Eden区和Survivor区,Survivor区有分为From Space和To Space。Eden区是对象最初分配到的地方;默认情况下,From Space和To Space的区域大小相等。JVM进行Minor GC时,将Eden中还存活的对象拷贝到Survivor区中,还会将Survivor区中还存活的对象拷贝到Tenured区中。在这种GC模式下,JVM为了提升GC效率, 将Survivor区分为From Space和To Space,这样就可以将对象回收和对象晋升分离开来。新生代的大小设置有2个相关参数:
-Xmn — 设置新生代内存大小。
-XX:SurvivorRatio — 设置Eden与Survivor空间的大小比例
老年代(Old Generation): 当 OLD 区空间不够时, JVM 会在 OLD 区进行 major collection;完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”Out of memory错误”
三、String类型的深度解析
让我们从Java数据类型开始说起吧!Java数据类型通常(分类方法多种多样)从整体上可以分为两大类:基础类型和引用类型,基础类型的变量持有原始值,引用类型的变量通常表示的是对实际对象的引用,其值通常为对象的内存地址。对于基础类型和引用类型的细分,直接上图吧,大家看了一目了然。当然,下图也仅仅只是其中的一种分类方式。
(原文图丢失)
针对上面的图,有3点需要说明:
char类型可以单独出来形成一类,很多基本类型的分类为:数值类型、字符型(char)和bool型。
returnAddress类型是一个Java虚拟机在内部使用的类型,被用来实现Java程序中的finally语句。
String类型在上图的什么位置?yes,属于引用类型下面的类类型。下面开始对String类型的挖掘!
1、String的本质
打开String的源码,类注释中有这么一段话“S their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。这句话总结归纳了String的一个最重要的特点:String是值不可变(immutable)的常量,是线程安全的(can be shared)。
接下来,String类使用了final修饰符,表明了String类的第二个特点:String类是不可继承的。
下面是String类的成员变量定义,从类的实现上阐明了String值是不可变的(immutable)。
private final char value[];
因此,我们看String类的concat方法。实现该方法第一步要做的肯定是扩大成员变量value的容量,扩容的方法重新定义一个大容量的字符数组buf。第二步就是把原来value中的字符copy到buf中来,再把需要concat的字符串值也copy到buf中来,这样子,buf中就包含了concat之后的字符串值。下面就是问题的关键了,如果value不是final的,直接让value指向buf,然后返回this,则大功告成,没有必要返回一个新的String对象。但是。。。可惜。。。由于value是final型的,所以无法指向新定义的大容量数组buf,那怎么办呢?“return new String(0, count + otherLen, buf);”,这是String类concat实现方法的最后一条语句,重新new一个String对象返回。这下真相大白了吧!
总结:String实质是字符数组,两个特点:1、该类不可被继承;2、不可变性(immutable)。
2、String的定义方法
在讨论String的定义方法之前,先了解一下常量池的概念,前面在介绍方法区的时候已经提到过了。下面稍微正式的给一个定义吧。
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。常量池还具备动态性,运行期间可以将新的常量放入池中,String类的intern()方法是这一特性的典型应用。不懂吗?后面会介绍intern方法的。虚拟机为每个被装载的类型维护一个常量池,池中为该类型所用常量的一个有序集合,包括直接常量(string、integer和float常量)和对其他类型、字段和方法的符号引用(与对象引用的区别?读者可以自己去了解)。
String的定义方法归纳起来总共为三种方式:
使用关键字new,如:String s1 = new String(“myString”);
直接定义,如:String s1 = “myString”;
串联生成,如:String s1 = “my” + “String”;这种方式比较复杂,这里就不赘述了,请参见。
第一种方式通过关键字new定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间,保证常量池中只有一个“myString”常量,节省内存空间。然后在内存堆中开辟一块空间存放new出来的String实例,在栈中开辟一块空间,命名为“s1”,存放的值为堆中String实例的内存地址,这个过程就是将引用s1指向new出来的String实例。各位,最模糊的地方到了!堆中new出来的实例和常量池中的“myString”是什么关系呢?等我们分析完了第二种定义方式之后再回头分析这个问题。
第二种方式直接定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间。然后在栈中开辟一块空间,命名为“s1”,存放的值为常量池中“myString”的内存地址。常量池中的字符串常量与堆中的String对象有什么区别呢?为什么直接定义的字符串同样可以调用String对象的各种方法呢?
带着诸多疑问,我和大家一起探讨一下堆中String对象和常量池中String常量的关系,请大家记住,仅仅是探讨,因为本人对这块也比较模糊。
第一种猜想:因为直接定义的字符串也可以调用String对象的各种方法,那么可以认为其实在常量池中创建的也是一个String实例(对象)。String s1 = new String(“myString”);先在编译期的时候在常量池创建了一个String实例,然后clone了一个String实例存储在堆中,引用s1指向堆中的这个实例。此时,池中的实例没有被引用。当接着执行String s1 = “myString”;时,因为池中已经存在“myString”的实例对象,则s1直接指向池中的实例对象;否则,在池中先创建一个实例对象,s1再指向它。如下图所示:
这种猜想认为:常量池中的字符串常量实质上是一个String实例,与堆中的String实例是克隆关系。
第二种猜想也是目前网上阐述的最多的,但是思路都不清晰,有些问题解释不通。下面引用一段内容。
在解析阶段,虚拟机发现字符串常量”myString”,它会在一个内部字符串常量列表中查找,如果没有找到,那么会在堆里面创建一个包含字符序列[myString]的String对象s1,然后把这个字符序列和对应的String对象作为名值对( [myString], s1 )保存到内部字符串常量列表中。如下图所示:
如果虚拟机后面又发现了一个相同的字符串常量myString,它会在这个内部字符串常量列表内找到相同的字符序列,然后返回对应的String对象的引用。维护这个内部列表的关键是任何特定的字符序列在这个列表上只出现一次。
例如,String s2 = “myString”,运行时s2会从内部字符串常量列表内得到s1的返回值,所以s2和s1都指向同一个String对象。
这个猜想有一个比较明显的问题,红色字体标示的地方就是问题的所在。证明方式很简单,下面这段代码的执行结果,javaer都应该知道。
String s1 = new String(“myString”);
String s2 = “myString”;
System.out.println(s1 == s2);
//按照上面的推测逻辑,那么打印的结果为true;而实际上真实的结果是false,因为s1指向的是堆中String对象,而s2指向的是常量池中的String常量。
虽然这段内容不那么有说服力,但是文章提到了一个东西——字符串常量列表,它可能是解释这个问题的关键。
文中提到的三个问题,本文仅仅给出了猜想,请知道真正内幕的高手帮忙分析分析,谢谢!
堆中new出来的实例和常量池中的“myString”是什么关系呢?
常量池中的字符串常量与堆中的String对象有什么区别呢?
为什么直接定义的字符串同样可以调用String对象的各种方法呢?
3、String、StringBuffer、StringBuilder的联系与区别
上面已经分析了String的本质了,下面简单说说StringBuffer和StringBuilder。
StringBuffer和StringBuilder都继承了抽象类AbstractStringBuilder,这个抽象类和String一样也定义了char[] value和int count,但是与String类不同的是,它们没有final修饰符。因此得出结论:String、StringBuffer和StringBuilder在本质上都是字符数组,不同的是,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以这就是为什么在进行大量字符串连接运算时,不推荐使用String,而推荐StringBuffer和StringBuilder。那么,哪种情况使用StringBuffe?哪种情况使用StringBuilder呢?
关于StringBuffer和StringBuilder的区别,翻开它们的源码,下面贴出append()方法的实现。
面第一张图是StringBuffer中append()方法的实现,第二张图为StringBuilder对append()的实现。区别应该一目了然,StringBuffer在方法前加了一个synchronized修饰,起到同步的作用,可以在多线程环境使用。为此付出的代价就是降低了执行效率。因此,如果在多线程环境可以使用StringBuffer进行字符串连接操作,单线程环境使用StringBuilder,它的效率更高。
四、参考文献
如果使用的是intellij idea,pom文件引入依赖的同时,必须导入插件lombok
微信fcd698
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:ImportNew.
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2018 ImportNew内存与String--(**)java&中变量存储位置的区别
&&http://blog.csdn.net/aaas6d/article/details/8251456
http://www.cnblogs.com/heima-jieqi/archive//2440086.html
&&http://www.cnblogs.com/mailingfeng/archive//2340086.html
&栈:包括java虚拟机栈(传统意义的栈)与本地方法栈。这里介绍java虚拟机栈:存放基本类型的变量数据、动态链接、方法出口、对象的引用等,但对象本身不存放在栈中,而是存放在堆(new
出来的对象)或者常量池中。&
  当在一段代码块定义一个变量时,Java就在栈中
为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。
堆:内存用来存放由new创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。方法中的局部变量使用final修饰后,也是放在堆中,而不是栈中(注意final修饰的字符串常量除外)。
方法区(又称静态存储区):用于存储已被虚拟机加载的类信息、常量、静态变量、编译后的代码等数据。包括静态域、常量池等。其中静态域存放的是&static
定义的变量;常量池存放字符串常量、public
static final修饰的基本类型常量。
& & 堆与栈
  这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性,但缺点是,由于要在运行时动态
分配内存,存取速度较慢。
  栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是
确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double,
boolean, char)和对象句柄(引用)。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
  Java代码
  int a = 3;
  int b = 3;
  编译器先处理int a =
3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b
3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。另外注意这里的a是一个指向int类型的引用,指向3这个字面值,这些字面值的数据,由于大小可知,生存期可知(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中,也就是说字面值3存储在栈中。
  这时,如果再令
a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响
  要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b,
它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
1.int i1 = 9; &
2.int i2 = 9; &
3.int i3 = 9; &&
4.public static final int INT1 = 9; &
5.public static final int INT2 = 9; &
6.public static final int INT3 = 9; &
对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。&
形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。&
成员变量存储在堆中的对象里面,由垃圾回收器负责回收。&
常量池 (constant pool)
常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如:
  ◆类和接口的全限定名;
  ◆字段的名称和描述符;
  ◆方法和名称和描述符。
如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。请问如何获取到JAVA对象所在的内存地址
[问题点数:40分,结帖人p3vsea2002]
本版专家分:0
结帖率 97.06%
CSDN今日推荐
本版专家分:84
本版专家分:11942
本版专家分:2864
本版专家分:1174
本版专家分:0
本版专家分:143
本版专家分:35901
本版专家分:4806
本版专家分:2864
本版专家分:95
本版专家分:236
本版专家分:0
本版专家分:3290
本版专家分:0
本版专家分:1320
本版专家分:832
本版专家分:0
本版专家分:4806
本版专家分:90
本版专家分:90
本版专家分:78
本版专家分:68
本版专家分:32
本版专家分:3290
本版专家分:616
本版专家分:1834
本版专家分:0
本版专家分:84
本版专家分:84
本版专家分:39
本版专家分:0
本版专家分:145
本版专家分:20
匿名用户不能发表回复!
其他相关推荐}

我要回帖

更多关于 java获取对象的类型 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信