博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 基础:JVM虚拟机结构
阅读量:4280 次
发布时间:2019-05-27

本文共 8106 字,大约阅读时间需要 27 分钟。

一、JVM结构

1.Class Loader:依据特定格式,加载class文件到内存

2.Execution Engine:对加载的二进制字节码命令进行解析
3.Native Interface:融合不同开发语言的原生库为Java所用
4.Runtime Data Area:JVM内存空间结构模型

二、JVM内存空间结构模型

1.内存模型结构

 

(1)    线程私有
程序计数器(字节码指令)、虚拟机栈(java方法)、本地方法栈(native方法)
(2)    线程共享
MetaSpace、Java堆

2. 线程私有:程序计数器(Program Counter Register)

作用:

(1)线程独立拥有计数器,是当前线程所执行的字节码行号指示器(逻辑)
(2)改变计数器的值来选取当前线程需要执行的下一条字节码指令
(3)线程独有,即一个线程拥有一个计数器
(4)对Java方法计数,如果是Native方法则计数器值为Undefined
(5)不会发生内存泄漏

3. 线程私有:Java虚拟机栈

(1)作用:java方法执行的内存模型,包含多个栈帧

(2)程序执行过程:对于当前线程,每个方法会产生1个栈帧,当方法执行结束后,该栈帧取消。而每个栈帧中包含:局部变量表、操作栈、动态链接、返回地址等等。
 
 
(3)    当前栈中包含内容
局部变量表:包含了执行过程中的所有变量(boolen,char,string等等)
操作数栈:操作数栈:入栈、出栈、复制、交换、产生消费变量(4)    实例实现当前线程调用过程
测试类:MemoryCodeSample.java

package com.spring.ioc.c5;public class MemoryCodeSample {    public static int add(int a,int b){        int c=0;        c=a+b;        return c;    }}
  • 编译:

src\main\java\com\spring\ioc\c5>javap -verbose MemoryCodeSample.class

  • 反编译:

src\main\java\com\spring\ioc\c5>javap -verbose MemoryCodeSample.class

  • 结果:

Classfile …/src/main/java/com/spring/ioc/c5/MemoryCodeSample.class

  Last modified 2019-12-11; size 292 bytes
  MD5 checksum 6d23ead3e45557a26587f2d70fbb9ee5
  Compiled from "MemoryCodeSample.java"
public class com.spring.ioc.c5.MemoryCodeSample            //描述类信息
  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/spring/ioc/c5/MemoryCodeSample
   #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               MemoryCodeSample.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               com/spring/ioc/c5/MemoryCodeSample
  #14 = Utf8               java/lang/Object
{
  public com.spring.ioc.c5.MemoryCodeSample();
    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 3: 0

  public static int add(int, int);

    descriptor: (II)I                            // 接受两个int输入,返回也是一个int
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=2            // 栈容量2,本地变量3,参数2个
         0: iconst_0                        // 把0压入操作数栈顶;同时获取输入的局部变量(1,2)
         1: istore_2                        // 把操作数栈顶元素取出,放入局部变量temp[2]位置
         2: iload_0                        // 把局部变量temp[0]压入操作数栈顶
         3: iload_1                        // 把局部变量temp[1]压入操作数栈顶
         4: iadd                            // 把操作数栈中元素加和,压入操作数栈顶
         5: istore_2                        // 把操作数栈顶元素取出,放入局部变量temp[2]
         6: iload_2                        // 把局部变量temp[2]压入操作数栈中
         7: ireturn                        // 把操作数栈顶元素返回
      LineNumberTable:
        line 6: 0                                //代码第6行,对应字节码第0行
        line 7: 2                                //代码第7行,对应字节码第2行
        line 8: 6                                //代码第8行,对应字节码第6行
}
SourceFile: "MemoryCodeSample.java"

具体过程add(1+2)实例

4. 线程私有:本地方法栈

与虚拟机栈类似,主要用于标注native方法

5.线程共享:元空间(MetaSpace)

(1)    作用:存储class基本信息,包括java对象的method和field等

(2)    元空间使用的是本地内存,而永久代是使用jvm内存
(3)    元空间(MetaSpace)与永久代(PermGen)区别
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
 
参考:https://www.cnblogs.com/secbro/p/11718987.html

6.线程共享:java堆

(1)堆内存分类:根据对象存活的周期不同,把堆内存划分为:新生代、老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存分代策略。

(2)作用:堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。
给堆内存分代是为了提高对象内存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率。
有了内存分代,新创建的对象会在新生代中分配内存;经过多次回收仍然存活下来的对象存放在老年代中;静态属性、类信息等存放在永久代中。新生代中的对象存活时间短,只需要在新生代区域中频繁进行GC;老年代中对象生命周期长,内存回收的频率相对较低,不需要频繁进行回收;永久代中回收效果太差,一般不进行垃圾回收。还可以根据不同年代的特点采用合适的垃圾收集算法。分代收集大大提升了收集效率,这些都是内存分代带来的好处。

(2)Java堆的分配区域

  • JAVA1.8之前

参考:https://www.cnblogs.com/guanghe/p/10524314.html

  • JAVA1.8之后

参考:

  • 元空间和永久代之前区别

       元空间并不在虚拟机中而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

四、问题

1.递归由于调度栈过深,导致超过虚拟机栈帧深度会引发java.lang.StackOverflowError异常

(1)代码:斐波那契数列

package com.spring.ioc.c5;public class Fibonacci {    public static int  fibonacci(int n){        if(n==0) return 0;        if(n==1) return 1;        return fibonacci(n-1)+fibonacci(n-2);    }    public static void main(String[] args) {        System.out.println(fibonacci(1));        System.out.println(fibonacci(2));        System.out.println(fibonacci(3));        System.out.println(fibonacci(4));        System.out.println(fibonacci(5));        System.out.println(fibonacci(1000000));    }}

报错:

1
1
2
3
5
Exception in thread "main" java.lang.StackOverflowError

(2)解决:限制递归次数,或者使用循环替换递归

2.内存不够导致java.lang.OutOfMemoryError问题

(1)代码

package com.spring.ioc.c5;public class stackLeak {    public void stackLeakByThread(){        while (true){            new Thread(){                public void run(){                    while (true){                    }                }            }.start();        }    }}

(2)报错:(警告:绝对不建议尝试,可能引起死机)

Exception in thread "main"java.lang OutOfMemoryError: unable to create new native thread

五、堆内存调优

1.堆内存调优参数

(1)参数

Xms:设置初始分配大小,默认为物理内存的“1/64”

Xmx:最大分配内存,默认为物理内存的“1/4”
-XX:+PrintGCDetails 输出详细的GC处理日志

(2)实例

-》代码

package com.algorithm.learn.test;import java.util.Random;public class test1 {    public static void main(String[] args) {        String str="hello world";        while (true){            str+=str+new Random().nextInt()+new Random().nextInt(88888);        }    }}

-》参数:-Xms8m -Xmx8m -XX:+PrintGCDetails

-》结果

[GC (Allocation Failure) [PSYoungGen: 1443K->481K(2048K)] 1443K->713K(7680K), 0.0052305 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 1605K->511K(2048K)] 1837K->1015K(7680K), 0.0009143 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1990K->288K(2048K)] 3452K->1990K(7680K), 0.0008724 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 1283K->0K(2048K)] [ParOldGen: 5538K->1561K(5632K)] 6822K->1561K(7680K), [Metaspace: 3112K->3112K(1056768K)], 0.0068044 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 1024K->96K(2048K)] 4503K->3574K(7680K), 0.0004818 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 96K->64K(2048K)] 3574K->3542K(7680K), 0.0003641 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 64K->0K(2048K)] [ParOldGen: 3478K->3480K(5632K)] 3542K->3480K(7680K), [Metaspace: 3125K->3125K(1056768K)], 0.0090112 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 3480K->3480K(7680K), 0.0002866 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 3480K->3464K(5632K)] 3480K->3464K(7680K), [Metaspace: 3125K->3125K(1056768K)], 0.0078615 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space	at java.util.Arrays.copyOf(Arrays.java:3332)	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)	at java.lang.StringBuilder.append(StringBuilder.java:208)	at com.algorithm.learn.test.test1.main(test1.java:30)	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)	at java.lang.reflect.Method.invoke(Method.java:498)	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)Heap PSYoungGen      total 2048K, used 57K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)  eden space 1536K, 3% used [0x00000000ffd80000,0x00000000ffd8e6f8,0x00000000fff00000)  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen       total 5632K, used 3464K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)  object space 5632K, 61% used [0x00000000ff800000,0x00000000ffb621a8,0x00000000ffd80000) Metaspace       used 3194K, capacity 4494K, committed 4864K, reserved 1056768K  class space    used 348K, capacity 386K, committed 512K, reserved 1048576K

六、参考

1.面试问题:你了解Java内存结构么(Java7、8、9内存结构的区别)

 

你可能感兴趣的文章
安卓与C代码 (一) 安卓中添加C可执行程序源码
查看>>
I2C (六) linux I2C 用户空间驱动
查看>>
蓝牙 (二) BLE
查看>>
安卓 (一) 怎么添加文件进安卓
查看>>
技术教程网址
查看>>
使用GDB调试程序
查看>>
使用core dump查看程序运行异常
查看>>
Linux应用程序在内存地址布局
查看>>
Linux应用编程之静态库和动态库
查看>>
静态链接库设计
查看>>
与时间相关的函数编程
查看>>
Linux进程控制相关概念
查看>>
c标准中的预定义宏
查看>>
*(volatile unsigned long *) 语法
查看>>
Linux多进程程序设计
查看>>
Linux进程间通讯基础
查看>>
Linux信号通讯编程
查看>>
信号量同步编程
查看>>
共享内存通讯编程
查看>>
Linux消息队列通讯编程
查看>>