內存尋址過(guò)程
我們知道JVM是內存中的虛擬機,主要使用內存進(jìn)行存儲,所有類(lèi)、類(lèi)型、方法,都是在內存中,這決定著(zhù)我們的程序運行是否健壯、高效。
在Java虛擬機棧中,一個(gè)棧幀對應一個(gè)方法,,方法執行時(shí)會(huì )在虛擬機棧中創(chuàng )建一個(gè)棧幀,而且當前虛擬機棧只能有一個(gè)活躍的棧幀,并且處于棧頂,當前方法結束后,可能會(huì )將返回值返回給調用它的方法,而自己將會(huì )被彈出棧(即銷(xiāo)毀),下一個(gè)棧頂將會(huì )被執行。
舉例說(shuō)明:
ByteCodeSample.java
package com.mtli.jvm.model;/** * @Description:測試JVM內存模型 * @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; }}對其進(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// 描述類(lèi)信息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 // 以上是常量池(線(xiàn)程共享){ 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類(lèi)型變量 flags: ACC_PUBLIC, ACC_STATIC // 描述方法權限和類(lèi)型 Code: stack=2, locals=3, args_size=2 // 操作數棧深度 、 容量 、參數數量 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行對應我們代碼中的第10行 line 12: 2 line 13: 6}SourceFile: 'ByteCodeSample.java'執行add(1,2)
以下是程序在JVM虛擬機棧中的執行過(guò)程
執行完之后,當前線(xiàn)程虛擬機棧的棧幀會(huì )彈出,對應的其他方法與當前棧幀的連接釋放、引用釋放,它的下一個(gè)棧幀成為棧頂。
我們知道,一個(gè)棧幀對應一個(gè)方法,存放棧幀的線(xiàn)程虛擬棧是有深度限制的,我們調用遞歸方法,每遞歸一次,就會(huì )創(chuàng )建一個(gè)新的棧幀壓入虛擬棧,當超出限度后,就會(huì )報此錯誤。
舉例說(shuō)明:
package com.mtli.jvm.model;/** * @Description:斐波那契 * F(0)=0,F(1)=1,當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 }}結果:
解決方法是限制遞歸次數,或者直接用循環(huán)解決。
還有就是,由JVM管理的虛擬機棧數量也是有限的,也就是線(xiàn)程數量也是有限定。
由于棧幀在方法返回后會(huì )自動(dòng)釋放,所有棧是不需要GC來(lái)回收的。
元空間(MetaSpace)在jdk1.7之前是屬于永久代(PermGen)的,兩者的作用就是記錄class的信息,jdk1.7中,永久代被移入堆中解決了前面版本的永久代分配內存不足時(shí)報出的OutOfMemoryError,jdk1.8之后元空間替代了永久代。

我們來(lái)看下面這個(gè)例子:


說(shuō)到這里我們不得不提一下String.intern()方法在jdk版本變更中的不同
String s = new String('a');s.intern();JDK6:當調用intern方法時(shí),如果字符串常量池先前已創(chuàng )建出該字符串對象,則返回池中的該字符串的引用。否則,將此字符串對象添加到字符串常量池中,并且返回該字符串對象的引用。
JDK6 :當調用intern方法時(shí),如果字符串常量池先前已創(chuàng )建出該字符串對象,則返回池中的該字符串的引用。否則,如果該字符串對象已經(jīng)存在于Java堆中,則將堆中,則將堆中對此對象的引用添加到字符串常量池中,并且返回該引用;如果堆中不存在,則在池中創(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下運行結果為
falsetrue分析:
jdk1.6下結果:
falsefalse第一個(gè)false跟上邊的一樣,第二個(gè)false是因為jdk1.6的intern()發(fā)現常量池中沒(méi)有'aa',則直接將此字符串對象添加到常量池中,兩個(gè)'aa'的地址是不一樣的,一個(gè)是堆中的一個(gè)是常量池中的,故結果也是false。
聯(lián)系客服