內(nèi)存尋址過(guò)程
我們知道JVM是內(nèi)存中的虛擬機(jī),主要使用內(nèi)存進(jìn)行存儲(chǔ),所有類、類型、方法,都是在內(nèi)存中,這決定著我們的程序運(yùn)行是否健壯、高效。
在Java虛擬機(jī)棧中,一個(gè)棧幀對(duì)應(yīng)一個(gè)方法,,方法執(zhí)行時(shí)會(huì)在虛擬機(jī)棧中創(chuàng)建一個(gè)棧幀,而且當(dāng)前虛擬機(jī)棧只能有一個(gè)活躍的棧幀,并且處于棧頂,當(dāng)前方法結(jié)束后,可能會(huì)將返回值返回給調(diào)用它的方法,而自己將會(huì)被彈出棧(即銷毀),下一個(gè)棧頂將會(huì)被執(zhí)行。
舉例說(shuō)明:
ByteCodeSample.java
package com.mtli.jvm.model;/** * @Description:測(cè)試JVM內(nèi)存模型 * @Author: Mt.Li * @Create: 2020-04-26 17:47 */public class ByteCodeSample { public static int add(int a , int b) { int c= 0; c = a b; return c; }}
對(duì)其進(jìn)行編譯生成.class文件
javac com/mtli/jvm/model/ByteCodeSample.java
然后用javap -verbose 進(jìn)行反編譯
javap -verbose com/mtli/jvm/model/ByteCodeSample.class
生成如下:
Classfile /E:/JavaTest/javabasic/java_basic/src/com/mtli/jvm/model/ByteCodeSample.class Last modified 2020-4-26; size 289 bytes MD5 checksum 2421660bb241239f1a67171bb771521f Compiled from 'ByteCodeSample.java'public class com.mtli.jvm.model.ByteCodeSample minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER// 描述類信息Constant pool: #1 = Methodref #3.#12 // java/lang/Object.'<init>':()V #2 = Class #13 // com/mtli/jvm/model/ByteCodeSample #3 = Class #14 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 add #9 = Utf8 (II)I #10 = Utf8 SourceFile #11 = Utf8 ByteCodeSample.java #12 = NameAndType #4:#5 // '<init>':()V #13 = Utf8 com/mtli/jvm/model/ByteCodeSample #14 = Utf8 java/lang/Object // 以上是常量池(線程共享){ public com.mtli.jvm.model.ByteCodeSample(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object.'<init>':()V 4: return LineNumberTable: line 8: 0// 以上是初始化過(guò)程 public static int add(int, int); descriptor: (II)I // 接收兩個(gè)int類型變量 flags: ACC_PUBLIC, ACC_STATIC // 描述方法權(quán)限和類型 Code: stack=2, locals=3, args_size=2 // 操作數(shù)棧深度 、 容量 、參數(shù)數(shù)量 0: iconst_0 1: istore_2 2: iload_0 3: iload_1 4: iadd 5: istore_2 6: iload_2 7: ireturn LineNumberTable: line 10: 0 // 這里的第0行對(duì)應(yīng)我們代碼中的第10行 line 12: 2 line 13: 6}SourceFile: 'ByteCodeSample.java'
執(zhí)行add(1,2)
以下是程序在JVM虛擬機(jī)棧中的執(zhí)行過(guò)程
執(zhí)行完之后,當(dāng)前線程虛擬機(jī)棧的棧幀會(huì)彈出,對(duì)應(yīng)的其他方法與當(dāng)前棧幀的連接釋放、引用釋放,它的下一個(gè)棧幀成為棧頂。
我們知道,一個(gè)棧幀對(duì)應(yīng)一個(gè)方法,存放棧幀的線程虛擬棧是有深度限制的,我們調(diào)用遞歸方法,每遞歸一次,就會(huì)創(chuàng)建一個(gè)新的棧幀壓入虛擬棧,當(dāng)超出限度后,就會(huì)報(bào)此錯(cuò)誤。
舉例說(shuō)明:
package com.mtli.jvm.model;/** * @Description:斐波那契 * F(0)=0,F(1)=1,當(dāng)n>=2的時(shí)候,F(n) = F(n-1) F(n-2), * F(2) = F(1) F(0) = 1,F(3) = F(2) F(1) = 1 1 = 2 * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34... * @Author: Mt.Li * @Create: 2020-04-26 18:33 */public class Fibonacci { public static int fibonacci(int n) { if(n>=0){ if(n == 0) {return 0;} if(n == 1) {return 1;} return fibonacci(n-1) fibonacci(n-2); } return n; } public static void main(String[] args) { System.out.println(fibonacci(0)); System.out.println(fibonacci(1)); System.out.println(fibonacci(2)); System.out.println(fibonacci(3)); System.out.println(fibonacci(1000000)); // java.lang.StackOverflowError }}
結(jié)果:
解決方法是限制遞歸次數(shù),或者直接用循環(huán)解決。
還有就是,由JVM管理的虛擬機(jī)棧數(shù)量也是有限的,也就是線程數(shù)量也是有限定。
由于棧幀在方法返回后會(huì)自動(dòng)釋放,所有棧是不需要GC來(lái)回收的。
元空間(MetaSpace)在jdk1.7之前是屬于永久代(PermGen)的,兩者的作用就是記錄class的信息,jdk1.7中,永久代被移入堆中解決了前面版本的永久代分配內(nèi)存不足時(shí)報(bào)出的OutOfMemoryError,jdk1.8之后元空間替代了永久代。
我們來(lái)看下面這個(gè)例子:
說(shuō)到這里我們不得不提一下String.intern()方法在jdk版本變更中的不同
String s = new String('a');s.intern();
JDK6:當(dāng)調(diào)用intern方法時(shí),如果字符串常量池先前已創(chuàng)建出該字符串對(duì)象,則返回池中的該字符串的引用。否則,將此字符串對(duì)象添加到字符串常量池中,并且返回該字符串對(duì)象的引用。
JDK6 :當(dāng)調(diào)用intern方法時(shí),如果字符串常量池先前已創(chuàng)建出該字符串對(duì)象,則返回池中的該字符串的引用。否則,如果該字符串對(duì)象已經(jīng)存在于Java堆中,則將堆中,則將堆中對(duì)此對(duì)象的引用添加到字符串常量池中,并且返回該引用;如果堆中不存在,則在池中創(chuàng)建該字符串并返回其引用
我們看一個(gè)例子:
public class InternDifference { public static void main(String[] args) { String s = new String('a'); s.intern(); String s2 = 'a'; System.out.println(s == s2); String s3 = new String('a') new String('a'); s3.intern(); String s4 = 'aa'; System.out.println(s3 == s4); }}
jdk1.8下運(yùn)行結(jié)果為
falsetrue
分析:
jdk1.6下結(jié)果:
falsefalse
第一個(gè)false跟上邊的一樣,第二個(gè)false是因?yàn)閖dk1.6的intern()發(fā)現(xiàn)常量池中沒(méi)有'aa',則直接將此字符串對(duì)象添加到常量池中,兩個(gè)'aa'的地址是不一樣的,一個(gè)是堆中的一個(gè)是常量池中的,故結(jié)果也是false。
聯(lián)系客服