java虚拟机_《深入理解Java虚拟机》:Java内存区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域称作运行时数据区域,分为5个部分:程序计数器、虚拟机栈、本地方法栈、堆、方法区。

这是在网上找的一张图,今天就来把各个区的用途和创建销毁时机来说一下。

da05c3b43a7646376361fda2448dabbf.png

1、程序计数器:它是当前程序执行的字节码的行号指示器,jvm执行引擎具体要执行哪一行指令,是由程序计数器来指示的。

javap是jkd自带的反编译命令,在命令行执行以下命令,得到MyStack的字节码文件,程序计数器指示的就是#1,#2,#3之类的行号:

javap -verbose MyStack.class > MyStack.txt
Constant pool:
   #1 = Class              #2             //  com/bill99/MyStack
   #2 = Utf8               com/bill99/MyStack
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          //  java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          //  "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/bill99/MyStack;
  #14 = Utf8               print
  #15 = Fieldref           #16.#18        //  java/lang/System.out:Ljava/io/PrintStream;
  #16 = Class              #17            //  java/lang/System
  #17 = Utf8               java/lang/System
  #18 = NameAndType        #19:#20        //  out:Ljava/io/PrintStream;
  #19 = Utf8               out
  #20 = Utf8               Ljava/io/PrintStream;
  #21 = String             #22            //  print()
  #22 = Utf8               print()
  #23 = Methodref          #24.#26        //  java/io/PrintStream.println:(Ljava/lang/String;)V
  #24 = Class              #25            //  java/io/PrintStream
  #25 = Utf8               java/io/PrintStream
  #26 = NameAndType        #27:#28        //  println:(Ljava/lang/String;)V
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Utf8               main
  #30 = Utf8               ([Ljava/lang/String;)V
  #31 = Methodref          #1.#9          //  com/bill99/MyStack."<init>":()V
  #32 = Methodref          #1.#33         //  com/bill99/MyStack.print:()V
  #33 = NameAndType        #14:#6         //  print:()V
  #34 = Utf8               args
  #35 = Utf8               [Ljava/lang/String;
  #36 = Utf8               myStack
  #37 = Utf8               SourceFile
  #38 = Utf8               MyStack.java

2、虚拟机栈:每个方法在执行的时候都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接、方法出口等信息然后压入虚拟机栈,每一个方法结束对应出栈的操作。

如下示例代码,debug调用栈也是main()方法先入栈,然后print()入栈,print()执行结束出栈,main()执行。

如下public class MyStack
{

	public void print(){
		System.out.println("print()");
	}
	public static void main(String[] args) {
		MyStack myStack = new MyStack();
		myStack.print();
	}
}

f9309285dadc2914e866fce5e48c1a54.png

这块区域常见异常有两种:线程请求栈的深度大于虚拟机栈所允许的深度,将抛出StackOverflowError;栈扩展的时候无法申请到足够内存空间,则抛出OutOfMemoryError。

改写一下print()方法,让它不断递归调用自己,可以看到栈深度到1万以上抛异常了。

private static int depth = 0;
public void print(){
		System.out.println("print()"+(++depth));
		print();
}

print()11422
Exception in thread "main" java.lang.StackOverflowError

3、本地方法栈:由于java代码本身的限制,有些和操作系统直接交互的方法,可能是基于c或者c++编写的,java代码可以通过本地方法间接的去调用操作系统底层的一些功能。本地方法运行时使用的内存空间就是本地方法栈。

比如Thread类的一些方法,如下:

public static native Thread currentThread();
public static native void yield();

4、Java堆:Java堆是虚拟机管理内存中最大的一块,是被所有线程共享的一块内存区域,常说的线程安全,指的就是堆中对象的线程安全问题。几乎所有new出来的对象都存放在这里,常说的内存分配和垃圾回收,针对的就是堆内存。

堆内存按两部分划分,可以是新生代和老年代,比例1:2;更细粒度划分,新生代又分Eden(伊甸园区),from,to,比例8:1:1.

1、创建一个MyHeap,这个对象有一个私有变量byte1,占1M内存

public class MyHeap
{

	private static final int MB = 1024 * 1024;
  byte[] byte1 = new byte[MB];
	public static void main(String[] args) {
		List<MyHeap> list = new ArrayList<>();
		MyHeap myStack = new MyHeap();
		list.add(myStack);
	}
}

2、配置堆内存初始值和最大值为20M,其中新生代占10M,并且打印堆内存信息

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

3、分析运行结果:

可以看出新生代PSYoungGen显示的内存总大小是9216K,为什么不是设置的10M呢?是因为from和to区总有一个是空闲的,不能用来存储对象的(因为垃圾回收需要复制,即从from复制到to,to必须和from空间一样大并且空闲,才能接收from的对象)。MyHeap对象占用了Eden2026K内存,2026K/8192K=24%;

老年代ParOldGen有堆内存-新生代:20M-10M=10240K;

永久代PSPermGen有20M。

Heap
 PSYoungGen      total 9216K, used 2026K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 24% used [0x00000000ff600000,0x00000000ff7faa88,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
 PSPermGen       total 21504K, used 2550K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
  object space 21504K, 11% used [0x00000000f9a00000,0x00000000f9c7d9f0,0x00000000faf00000)

5、方法区:与Java堆一样,是线程共享区域,主要存储类信息、常量、静态变量、即时编译器编译后的代码等数据。hotspot虚拟机实现中把此方法区又称作“永久代”。

运行时常量区:它是方法区的一部分。前面程序计数器字节码文件举例时,文件中Constant pool指的就是常量区,用于存放编译器生成的各种字面量和符号引用。

能进入常量池的内容不一定是编译器产生的,像String.intern();是在运行时把数据放入常量池的。

public static void main(String[] args) {
		String s1 = "a";//行号6
		String s2 = "b";//行号7
		String s3 = "ab";//行号8
		String s4 = s1+s2;//行号9
		System.out.println(s3==s4);//行号10
}//行号11

以上代码编译后的部分字节码如下,我们发现第9行代码编译成了new StringBuilder对象,而6,7,8行代码编译后分别是0,3,6对应的常量字符串,所以可以验证第11行输出肯定是false,因为s4对象在堆中,而s3在常量池。

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=3, locals=5, args_size=1
         0: ldc           #16                 // String a
         2: astore_1      
         3: ldc           #18                 // String b
         5: astore_2      
         6: ldc           #20                 // String ab
         8: astore_3      
         9: new           #22                 // class java/lang/StringBuilder
        12: dup           
        13: aload_1       
        14: invokestatic  #24                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        17: invokespecial #30                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        20: aload_2       
        21: invokevirtual #33                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #37                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: getstatic     #41                 // Field java/lang/System.out:Ljava/io/PrintStream;
        32: ldc           #20                 // String ab
        34: aload         4
        36: if_acmpne     43
        39: iconst_1      
        40: goto          44
        43: iconst_0      
        44: invokevirtual #47                 // Method java/io/PrintStream.println:(Z)V
        47: return        
      LineNumberTable:
        line 6: 0     
        line 7: 3
        line 8: 6
        line 9: 9
        line 10: 29
        line 11: 47

用一下神奇的String.intern(),再看看结果

public static void main(String[] args) {
		String s1 = "a";//行号6
		String s2 = "b";//行号7
		String s3 = "ab";//行号8
		String s4 = s1+s2;//行号9
		System.out.println(s3==s4);//行号10
    String s5 = s4.intern();
		System.out.println(s3==s5);
  
}//行号11

System.out.println("ab"==s5);就会输出true,因为s4.intern()方法是主动把字符串放入常量池,s5现在是指向常量池中的ab。

6、直接内存:在没有直接内存的情况下,磁盘I/O的内容,需要先从磁盘读取到系统内存,然后再从系统内存读取到Java堆内存;而直接内存是系统内存和Java堆内存的一块共享内存区域,避免了在Java堆和Native堆中来回复制数据,很大的提高了读写性能。

cdd1b85e0311b64b2e5ca0f1ac2dfb67.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值