定义全局常量量里面放的值还是地址;如果放的值:常量值是放在常量池中的,常量名是放在栈中的,他怎么能找到?

java高分局之jvm指令浅析
java高分局之jvm指令浅析
几个简单的指令
iconst:是把常量池中的值加载到操作数栈,比如:iconst_1把第一个常量加载到操作数栈。
ipush:这个指令和iconst_的功能相同,是指把常量n加载到操作数栈,n是作为参数传给ipush指令,n代表具体的常量值。
iload:把局部变量表中第n个变量加载到操作数栈。
istore:把操作数栈顶元素存入第n个局部变量。
iadd:把操作数栈顶两个元素相加,然后把结果重新存入栈顶。
操作数栈、常量池和局部变量表
一段小程序
public class Test {
public static void main(String[] args) {
int n = i +
字节码如下
分步解析字节码
上面的程序有编译后常量池中会存在两个值:0和1。局部变量表中会存在3个变量分别是:I,j和f,如下图所示:
编译后成为9行字节码指令,我们下面一步一步的看一下这个执行过程:
iconst_0:表示把第0个常量加载到操作数栈,可见常量池的下标是从0开始的。如下图所示:
istore_1:表示把操作数栈顶的元素存储到第一个局部变量表中,删除栈顶元素。局部变量表的下标是从1开始的?这是错误的,在这里因为main方法有参数,所以参数是局部变量表的第一个元素。所以局部变量表的下标也是从0开始的,局部变量表。如下图所示:
iconst_1:把下标为1的常量值加载到栈顶,如下图所示:
istore_2:把操作数栈顶元素存储到第二个局部变量,删除栈顶元素。如下图所示:
iload_1:加载第一个局部变量到栈顶,如下图所示:
iload_2:加载第二个局部变量到栈顶,其他栈中元素下移,如下图:
iadd:把栈顶的两个元素相加,然后删除栈顶两个元素,再把相加的结果压入栈顶,如下图所示:
istore_3:把栈顶元素存入第3个局部变量表中,如下图所示:
return:方法返回
没有更多推荐了,java 常量池 到底是在堆中还是栈中?_百度知道
java 常量池 到底是在堆中还是栈中?
请简单说明 谢谢
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
java常量池不在堆中也不在栈中,是独立的内存空间管理。 1. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。) 2. 堆:存放所有new出来的对象。 3. 常量池:存放字符串常量和基本类型常量(public static final)。对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
软件工程师
既不在堆中也不再栈中static静态变量,需要放在内存中的data segment中
本回答被提问者采纳
请参考:程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:(1) 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。(2) 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些Java数据要保存在堆栈里——特别是对象句柄,但Java对象并不放到其中。(3) 堆。一种常规用途的内存池(也在RAM区域),其中保存了Java对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!(4) 静态存储。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。(5) 常数存储。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。(6) 非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。Java 1.1提供了对Lightweight persistence的支持。未来的版本甚至可能提供更完整的方案
其他1条回答
为您推荐:
其他类似问题
您可能关注的内容
常量的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。当前位置浏览文章
每天进步一点点!今天主要理解一下栈帧中的内容,大部分为内容都比较容易了解,且做以下记录。首先详情以下栈帧的概念:栈帧(Stack Frame)是使用于支持虚拟机进行方法调使用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧中主要存储了5方面的内容:方法的局部变量表,操作数栈,动态连接,方法返回地址和少量额外的附加信息。在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因而一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。一个线程中的方法调使用链可可以会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(CurrentMethod)。执行引使用所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧的概念结构如下图所示:下面让我们来简单详情以下,栈帧中的五个方面。1. 局部变量表:是一组变量值存储空间,使用于存放方法参数和方法内部定义的局部变量。在方法的Code属性的max_locals数据项中,确定了该方法所需要分配的局部变量表的最大容量。局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位,slot的长度随解决器,操作系统和虚拟机的不同而变化。注意:Slot是能重使用的,当前字节码PC计数器的值已经超出了某个变量的作使用域,那这个变量对应的Slot即可以交给其余变量用。对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的Slot空间。虚拟机的引使用需要做到两点:一是从此引使用中直接或者间接地查找到对象在Java堆中的数据存放的起始地址索引;二是此引使用中直接或者间接地查找到对象所属数据类型在方法区中的存储的类型信息。局部变量表建立在线程的堆栈上,是线程私有的数据,无论读写两个连续的Slot能否为原子操作,都不会引起数据安全问题。局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。假如是64位数据类型的变量,会同时用n和n+1两个Slot。假如执行的是实例方法(非static的方法),那局部变量表中第0位索引的Slot默认是使用于传递方法所属对象实例的引使用。局部变量不像前面详情的类变量那样存在“准备阶段”,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予程序员定义的初始值。2. 操作数栈:操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。操作数栈的每一个元素能是任意的Java数据类型,32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。这里有一点需要注意的是,操作数栈中元素的数据类型必需与字节码指令的序列严格匹配。为了对栈帧进行优化,虚拟机会让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调使用时即可以共使用一部分数据。3. 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引使用,持有这个引使用是为了支持方法调使用过程中的动态连接(Dynamic Linking)。常量池中存有大量的符号引使用,字节码中的方法调使用指令就以常量池中指向方法的符号引使用作为参数。这些符号引使用一部分会在类加载阶段或者者第一次用的时候就转化为直接引使用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引使用,这部分称为动态连接。这块不知道大家对前面说的静态绑定和绑定有没有印象,其实道理都是差不多的。4. 方法返回地址:当一个方法开始执行后,只有两种方式能退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,如return,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得四处理,无论是Java虚拟机内部产生的异常,还是代码中用athrow字节码指令产生的异常,只需在本方法的异常表中没有搜索到匹配的异常解决器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。无论采使用何种退出方式,在方法退出之后,都需要返回到方法被调使用的位置,程序才可以继续执行;而方法异常退出时,返回地址是要通过异常解决器表来确定的。方法退出的过程实际上就等同于把当前栈帧出栈,因而退出时可可以执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(假如有的话)压入调使用者栈帧的操作数栈中,调整PC计数器的值以指向方法调使用指令后面的一条指令等。5. 附加信息:虚拟机规范允许具体的虚拟机实现添加少量规范里没有形容的信息到栈帧之中。以上即为栈帧的知识点,都是比较概念化的东西,大部分摘自《深入了解Java虚拟机》,这里权且记录,侵删。喜欢文章或者想一起学习的朋友能关注我,给我点赞,我将会持续升级,有什么疑问或者文中有不当之处请给我留言,真挚地希望可以与大家一起交流讨论,学习进步!07-09 2018
Java的Class信息中的常量池中类或接口的全限定名在加载类的解析阶段还会在JVM中存在吗,是不是换成了该类的内存地址偏移?
比如下面的截图中所示,最终解析后的这个CONSTANT_Class_info在JVM中的值是什么?他指向的哪个CONSTANT_Utf8_info解析后是什么样的呢?
"文章为作者独立观点, 不代表老订阅立场"
书名:《公司的坏话》作者:李天田(脱不花妹妹)出版社:北京大学出版社java堆、栈、字符串常量池
一、java内存模型
JVM主要管理两种类型内存:堆和非堆,堆内存(HeapMemory)是在
启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。
简单来说,非堆包含方法区、JVM内部处理或优化所需的内(如Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。
二、Java中六种不同的存储位置:
1. 寄存器(register)。 这是最快的存储区,因为它位于不同于其他存储区的地方&&处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
------最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.
2. 堆栈(stack)。位于通用RAM中,但通过它的&堆栈指针&可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中&&特别是对象引用,但是JAVA对象不存储其 中。
------存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)
3. 堆(heap)。一种通用性的内存池(也存在于RAM中),用于存放所以的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储数据需要更多的时间。
------存放所有new出来的对象。
4. 静态存储(staticstorage)。这里的&静态&是指&在固定的位置&。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。
------存放静态成员(static定义的)
5. 常量存储(constantstorage)。常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中
------存放字符串常量和基本类型常量(public static final)
6. 非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。
硬盘等永久存储空间 就速度来说,有如下关系:
寄存器 &堆栈& 堆& 其它
1. 栈(stack)与堆(heap)都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈中数据可以共享 堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。
三、Java中的数据类型
一种是基本类型(primitive types), 共有8种,即int,short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。
这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
栈中的数据可以共享:假设我们同时定义 int a = 3; intb = 3;编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与 b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。
举例如下: Java代码
public class Test {
public static void main(String[] args)
int d1=a1+b1;
Integerc = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(a1==b1); //true 结果1
System.out.println(c1==d1); //true 结果2
System.out.println(c==d); //true 结果3
System.out.println(e==f); //false 结果4
结果1:a1==b1如上面所述,会在栈中开辟存储空间存放数据。
结果2:首先它会在栈中创建一个变量为c1的引用,然后查找有没有字面值为2的地址,没找到,就开辟一个存放2这个字面值的地址,然后将c1指向2的地址,d1为两个字面值相加也为2,由于在栈中已经有2这个字面值,便将d1直接指向2的地址。这样,就出现了c1与d1同时均指向2的地址的情况。
在分析下面结果以前让我们先对Java自动拆箱和装箱做个了结:在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆中的Integer对象,(我们可以这样理解,系统已经把-128到127之间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。
结果3:由于3是在范围内所以是从缓存中取数据的,c和d指向同一个对象,结果为
结果4:由于321不是在范围内所以不是从缓存中取数据的而是单独有new对象,e和f并没有指向同一个对象,结果为
三、String是一个特殊的包装类数据。
一,String即可以用String str = new String(&abc&);的形式来创建,也可以用String str =&abc&;的形式来创建。
String str = &abc&创建对象的过程
1 首先在常量池中查找是否存在内容为&abc&字符串对象
2 如果不存在则在常量池中创建&abc&,并让str引用该对象
3 如果存在则直接让str引用该对象
注意:常量池属于类信息的一部分,而类信息反映到JVM内存模型中是对应存在于JVM内存模型的方法区,也就是说这个类信息中的常量池概念是存在于在方法区中。一般这种情况下,&abc&在编译时就被写入字节码中,所以class被加载时,JVM就为&abc&在常量池中分配内存,所以和静态区差不多。
Stringstr = new String(&abc&)创建实例的过程
1首先在常量池中查看是否存在内容为&abc&字符对象
2 若存在,str引用常量池中的&abc&对象
3 若不存在,则在字符串常量池中创建一个内容为&abc&的字符串对象,str引用该对象
4 然后new String(&abc&)在堆中创建一个常量池中此 &abc& 对象的拷贝对象并将new出来的字符串对象与字符串常量池中的对象联系起来
二,详细例子:
public static void main(String[] args) {
* 情景一:字符串池
* JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象;
* 并且可以被共享使用,因此它提高了效率。
* 由于String类是final的,它的值一经创建就不可改变。
* 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。
String s1 = &abc&;
//& 在字符串池创建了一个对象
String s2 = &abc&;
//& 字符串常量池中已经存在对象&abc&(共享),所以创建0个对象,累计创建一个对象
System.out.println(&s1== s2 : &+(s1==s2));
//& true 指向同一个对象,
System.out.println(&s1.equals(s2): & + (s1.equals(s2)));
//& true 值相等
* 情景二:关于new String(&&)
String s3 = new String(&abc&);
//& 创建了两个对象,一个存放在字符串池中,一个存在堆区中;
//& 还有一个对象引用s3存放在栈中
String s4 = new String(&abc&);
//& 字符串池中已经存在&abc&对象,所以只在堆中创建了一个对象
System.out.println(&s3== s4 : &+(s3==s4));
//&false s3和s4栈区引用的地址不同,堆区对象的地址不同;
System.out.println(&s3.equals(s4): &+(s3.equals(s4)));
//&true s3和s4的值相同
System.out.println(&s1== s3 : &+(s1==s3));
//&false 存放的地区不同,一个常量池中,一个堆区
System.out.println(&s1.equals(s3): &+(s1.equals(s3)));
//&true 值相同
* 情景三:
* 由于常量的值在编译的时候就被确定(优化)了。
* 在这里,&ab&和&cd&都是常量,因此变量str1的值在编译时就可以确定。
* 这行代码编译后的效果等同于: String str1 = &abcd&;
String str1 = &ab&+ &cd&; //1个对象
String str2 = &abcd&;
System.out.println(&str1= str2 : &+ (str1 == str2));
//&引用的常量池中同一个对象
* 情景四:
* 涉及到str2是变量(不全是常量)的相加,所以会生成新的对象,其内部实
* 现是先new一个StringBuilder,然后 append(str2),append(&c&);然后让
* str3引用toString()返回的对象
String str1 = &abc&;
String str2 = &ab&;
String str3 = str2+&c&;
System.out.println(str1==str3); // false
final String str1 = &abc&;
String str2 = &ab&;
String str3 = str2+&c&;
System.out.println(str1==str3); // true
* 情景五:
* intern() 方法可以返回该字符串在常量池中的对象的引用
* 一个初始为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法
* 时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方
* 法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回
* 此 String 对象的引用。
String str1 = &abc&;
String str2 = newString(&abc&).intern();
System.out.println(str1==str2);
/**情景六:
* 当我们将str1的值改为&bcd&时,JVM发现在常量池中没有存放该值的地址,
* 便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。
* 事实上String类被设计成为不可改变(immutable)的类。如果你要改变其值,* 可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的
* 地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟
* 占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影
String str1 = &abc&;
String str2 = &abc&;
str1 = &bcd&;
System.out.println(str1 + &,& + str2); //bcd, abc
System.out.println(str1==str2); //false}

我要回帖

更多关于 js定义全局常量 的文章

更多推荐

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

点击添加站长微信