文档章节

jvm原理解析--不疯魔不成活

骰子红豆豆
 骰子红豆豆
发布于 2018/04/20 05:02
字数 3704
阅读 323
收藏 22

java程序运行

*.java文件-->编译器-->*.class文件-->线程启动(main)-->jvm-->操作系统-->硬件

 

通过上面的流程我们可以看出java程序的执行顺序,那么jvm到底是什么,class文件到底是如何在jvm中运行就显得很重要了。

jvm原理

什么是jvm

openjdk源码地址http://hg.openjdk.java.net/jdk9

JVM是一个计算机模型,JVM对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括操作码操作数的语法(也就是cpu指令集)和数值、标识符的数值表示方式、以及Java类文件中的Java对象、常量缓冲池在JVM的存储映象。

JVM的组成

JVM指令系统、JVM寄存器、JVM 栈结构、JVM 碎片回收堆、JVM 存储区

JVM指令

Java指令也是由操作码和操作数两部分组成,与RISC CPU采用的编码方式是一致的,也就是精简指令集,目前UNIX、Linux、MacOS系统使用RISC,我们目前知道的x86架构CPU使用的CISC编码,也就是复杂指令集。

JVM寄存器

1.pc程序计数器

2.optop操作数栈顶指针

3.frame当前执行环境指针

4.vars指向当前执行环境中第一个局部变量指针

 

jvm的装载

windows操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境。

1.创建JVM装载环境和配置

2.装载JVM.dll(C:\Program Files\Java\jre1.8.0_151\bin\server linux在jre/lib/server下)

3.初始化JVM.dll并挂接到JNIENV(JNI调用接口)实例

4.调用JNIEnv实例装载并处理class类

JVM虚拟机相当于x86计算机系统,Java解释器相当于x86CPU

JVM运行数据

JVM定义了若干个程序执行期间使用的数据区域。这个区域里的一些数据在JVM启动的时候创建,在JVM退出的时候销毁。而其他的数据依赖于每一个线程,在线程创建时创建,在线程退出时销毁。分别有程序计数器,堆,栈,方法区,运行时常量池

  • 程序计数器:每个线程一旦被创建就拥有了自己的程序计数器。当线程执行Java方法的时候,它包含该线程正在被执行的指令的地址。但是若线程执行的是一个本地的方法,那么程序计数器的值就不会被定义。
  • 常量缓冲池和方法区:常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。对于这两种存储区域具体实现方式在JVM规格中没有明确规定。这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。
  • 栈:Java栈是JVM存储信息的主要方法。当JVM得到一个Java字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:

    局部变量对应vars寄存器指向该变量表中的第一个局部变量用于存储一个类的方法中所用到的局部变量。

    执行环境:对应frame寄存器的当前执行环境指针用于保存解释器对Java字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。执行环境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。

    操作数栈:对应optop寄存器的操作数栈顶指针,操作数栈用于存储运算所需操作数及运算的结果。

  • 堆:JVM中最大的,应用的对象和数据都是存在这个区域,这块区域也是线程共享的,也是 gc 主要的回收区,一个 JVM 实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行。

       垃圾回收机制(GC)只发生在线程共享区,也就是堆和方法区,栈不需要回收,线程销毁则栈也销毁,         也就是上图的heap space与method area会发生gc。

        通过上图可以发现heap space被分为两部分:

  • Young Generation:又分为Eden space所有的类都是在Eden space被new出来的。From区(Survivor 0 space)和To区(Survivor 1 space)。当Eden space空间用完时,程序又需要创建对象,JVM的垃圾回收器将对Eden space进行垃圾回收(Minor GC),将Eden space中的剩余对象移动到From区。若From区也满了,再对该区进行垃圾回收,然后移动到To区。那如果To区也满了呢,再移动到Old区。
  • Old Generation:若该区也满了,那么这个时候将产生Major GC(FullGCC),进行Tenured区的内存清理。若该区执行Full GC 之后发现依然无法进行对象的保存,产生异常java.lang.OutOfMemoryError: Java heap space
  1. Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
  2. 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。
  • Permanent Generation:是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。产生异常java.lang.OutOfMemoryError: PermGen space jdk1.8之后已经不会再报报这个错误了。因为类信息的卸载几乎很少发生,这样会影响GC的效率。于是PermGen便被拆分出去了。
  1. 程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。
  2. 大量动态反射生成的类不断被加载,最终导致Perm区被占满。

jvm的算法

由于算法篇幅太长具体算法可自行查阅资料,主要介绍gc算法发生在什么区。

分代搜集算法:是由复制算法、标记/整理、标记/清除算法共同组成

复制算法发生在Young Generation

标记/整理和标记/清除算法发生在Old Generation和Permanent Generation

java验证jvm

栈中一般存放的都是对象的指针和基本类型,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

栈数据可以共享

​
/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        int a=0;
        int b=0;
        System.out.print(a==b);
    }
}

​
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
true
Process finished with exit code 0

编译器先处理int a = 0;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为0的地址,没找到,就开辟一个存放0这个字面值的地址,然后将a指向0的地址。接着处理int b = 0;在创建完b的引用变量后,由于在栈中已经有0这个字面值,便将b直接指向0的地址。这样,就出现了a与b同时均指向0的情况

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        int a=0;
        int b=0;
        a=1;
        System.out.print("a="+a);
        System.out.print("b="+b);
        System.out.print(a==b);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" 
a=1b=0false
Process finished with exit code 0

再令a=1;那么,b不会等于1,还是等于0。在编译器内部,遇到a=1;时,它就会重新搜索栈中是否有1的字面值,如果没有,重新开辟地址存放1的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。 

String str = "abc"的工作原理

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
true

Process finished with exit code 0
/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        str1 = "bcd";
        System.out.println(str1 + "," + str2);
        System.out.println(str1==str2);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" 
bcd,abc
false

Process finished with exit code 0

赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";

        str1 = "bcd";

        String str3 = str1;
        System.out.println(str3);

        String str4 = "bcd";
        System.out.println(str1 == str4);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
bcd
true

Process finished with exit code 0

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用 str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

堆验证

String类 

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
false

Process finished with exit code 0

 以上代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是 享元模式的思想。

由于String类的性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

执行时间上寄存器 < 堆栈 < 堆 

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args) {
        String s1 = "ja";
        String s2 = "va";
        String s3 = "java";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);
        System.out.println(s3.equals(s4));
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java"
false
true

Process finished with exit code 0

是不是很矛盾啊!是不是又懵逼了?

打印false的原因是,java 重载了“+”,查看java字节码可以发现“+”其实是调用了StringBuilder 所以使用了“+”其实是生成了一个新的对象。所以(s3 == s4)打印false

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args){
        long maxMemory = Runtime.getRuntime().maxMemory();//返回Java虚拟机试图使用的最大内存量。
        Long totalMemory = Runtime. getRuntime().totalMemory();//返回jvm实例占用的内存。
        System.out.println("MAX_MEMORY ="+maxMemory +"(字节)、"+(maxMemory/(double)1024/1024) + "MB");
        System.out.println("TOTAL_ MEMORY = "+totalMemory +"(字节)"+(totalMemory/(double)1024/1024) + "MB");
    }
}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" -XX:+PrintGCDetails
MAX_MEMORY =1868038144(字节)、1781.5MB
TOTAL_ MEMORY = 126877696(字节)121.0MB
Heap
 PSYoungGen      total 37888K, used 3932K [0x00000000d6400000, 0x00000000d8e00000, 0x0000000100000000)
  eden space 32768K, 12% used [0x00000000d6400000,0x00000000d67d7320,0x00000000d8400000)
  from space 5120K, 0% used [0x00000000d8900000,0x00000000d8900000,0x00000000d8e00000)
  to   space 5120K, 0% used [0x00000000d8400000,0x00000000d8400000,0x00000000d8900000)
 ParOldGen       total 86016K, used 0K [0x0000000082c00000, 0x0000000088000000, 0x00000000d6400000)
  object space 86016K, 0% used [0x0000000082c00000,0x0000000082c00000,0x0000000088000000)
 Metaspace       used 3325K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0

将jvm堆初始值改小,触发gc回收

import java.util.Random;

/**
 * Created by liustc on 2018/4/20.
 */
public class JvmTest {

    public static void main(String[] args){
        long maxMemory = Runtime.getRuntime().maxMemory();//返回jvm试图使用的最大内存量。
        Long totalMemory = Runtime. getRuntime().totalMemory();//返回jvm实例的内存大小。
        System.out.println("MAX_MEMORY ="+maxMemory +"(字节)、"+(maxMemory/(double)1024/1024) + "MB");
        System.out.println("TOTAL_ MEMORY = "+totalMemory +"(字节)"+(totalMemory/(double)1024/1024) + "MB");
        String str = "www.baidu.com";
        while(true){
            str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
        }
    }}
"C:\Program Files\Java\jdk1.8.0_151\bin\java" -XX:+PrintGCDetails
MAX_MEMORY =1868038144(字节)、1781.5MB
TOTAL_ MEMORY = 126877696(字节)121.0MB
[GC (Allocation Failure) [PSYoungGen: 32247K->2729K(37888K)] 32247K->10124K(123904K), 0.0045031 secs] [Times: user=0.01 sys=0.03, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32912K->4469K(70656K)] 40307K->26638K(156672K), 0.0121112 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 66160K->776K(70656K)] 88329K->59879K(156672K), 0.0141096 secs] [Times: user=0.03 sys=0.02, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 776K->0K(70656K)] [ParOldGen: 59103K->37630K(116224K)] 59879K->37630K(186880K), [Metaspace: 3408K->3408K(1056768K)], 0.0143902 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 60370K->0K(70656K)] [ParOldGen: 96726K->74565K(172032K)] 157096K->74565K(242688K), [Metaspace: 3409K->3409K(1056768K)], 0.0598124 secs] [Times: user=0.08 sys=0.00, real=0.06 secs] 
[GC (Allocation Failure) [PSYoungGen: 60382K->32K(95744K)] 1257771K->1226968K(1463808K), 0.0227293 secs] [Times: user=0.06 sys=0.01, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(131584K)] 1226968K->1226968K(1499648K), 0.0037586 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(131584K)] [ParOldGen: 1226936K->355271K(483840K)] 1226968K->355271K(615424K), [Metaspace: 3409K->3409K(1056768K)], 0.1616835 secs] [Times: user=0.19 sys=0.09, real=0.16 secs] 
[GC (Allocation Failure) [PSYoungGen: 2499K->32K(158208K)] 1303306K->1300838K(1526272K), 0.0037952 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(158208K)] 1300838K->1300838K(1526272K), 0.0036491 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(158208K)] [ParOldGen: 1300806K->473463K(622080K)] 1300838K->473463K(780288K), [Metaspace: 3409K->3409K(1056768K)], 0.1641897 secs] [Times: user=0.30 sys=0.06, real=0.16 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(250880K)] 946230K->946230K(1618944K), 0.0027229 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(258560K)] 946230K->946230K(1626624K), 0.0027747 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(258560K)] [ParOldGen: 946230K->709846K(879104K)] 946230K->709846K(1137664K), [Metaspace: 3409K->3409K(1056768K)], 0.1013768 secs] [Times: user=0.28 sys=0.02, real=0.10 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(353280K)] 709846K->709846K(1721344K), 0.0049384 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
[PSYoungGen: 0K->0K(353280K)] [ParOldGen: 709846K->709816K(900608K)] 709846K->709816K(1253888K), [Metaspace: 3409K->3409K(1056768K)], 0.1792920 secs] [Times: user=0.39 sys=0.00, real=0.18 secs] 
Heap
	at java.util.Arrays.copyOf(Arrays.java:3332)
 PSYoungGen      total 353280K, used 14028K [0x00000000d6400000, 0x00000000ec700000, 0x0000000100000000)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
  eden space 352768K, 3% used [0x00000000d6400000,0x00000000d71b3070,0x00000000ebc80000)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
  from space 512K, 0% used [0x00000000ec680000,0x00000000ec680000,0x00000000ec700000)
	at java.lang.StringBuilder.append(StringBuilder.java:208)
  to   space 4608K, 0% used [0x00000000ebe00000,0x00000000ebe00000,0x00000000ec280000)
	at JvmTest.main(JvmTest.java:15)
 ParOldGen       total 1368064K, used 709816K [0x0000000082c00000, 0x00000000d6400000, 0x00000000d6400000)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  object space 1368064K, 51% used [0x0000000082c00000,0x00000000ae12e1e8,0x00000000d6400000)
 Metaspace       used 3440K, capacity 4494K, committed 4864K, reserved 1056768K
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  class space    used 377K, capacity 386K, committed 512K, reserved 1048576K
	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:140)

Process finished with exit code 1

 

© 著作权归作者所有

骰子红豆豆
粉丝 4
博文 6
码字总数 8202
作品 0
西安
程序员
私信 提问
Java 并发编程源码解析汇总篇

java并发编程,内存模型 java并发编程,volatile内存实现和原理 Java并发编程,并发基础 Java 并发编程,线程池(ThreadPoolExecutor)源码解析 Java并发编程,Executor 框架介绍 Java并发编...

郑加威
2018/12/23
0
0
高手问答第 144 期 — 轻量级数据库中间层 Sharding-JDBC 深度解析

OSCHINA 本期高手问答(2017 年 3 月 7 日 — 3 月 13 日)我们请来了 @terrymanu (张亮) 和大家探讨分布式数据库中间层的适用场景和自研的选型过程。 @terrymanu 张亮,目前是当当架构部负...

局长
2017/03/06
13.7K
68
【死磕Sharding-jdbc】— 死磕 Sharding-jdbc 精品合集

死磕 Sharding-jdbc 是 【阿飞哥】的精心力作,花费 4 个月,总共输出 22 篇文章,全部都是关于 Sharding-jdbc 的原理解析和源码分析,通俗易懂。 下图是 【阿飞哥】 的公众号,欢迎各位关注...

飞哥-Javaer
2018/08/26
0
0
《数据结构与算法系列》合集整理

《数据结构与算法系列》合集整理 整理来自博客园skywang12345,以下摘自作者介绍: “最近抽空整理了"数据结构和算法"的相关文章。在整理过程中,对于每种数据结构和算法分别给出"C"、"C++"...

kaixin_code
2018/12/01
188
0
结合JVM源码谈Java类加载器

一、前言 之前文章 Java 类加载器揭秘 从Java层面讲解了Java类加载器的原理,这里我们结合JVM源码在稍微深入讲解下。 二、Java类加载器的委托机制 Java 类加载器使用的是委托机制,也就是一个...

阿里加多
2018/04/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

[mycat]PartitionByString分片报错

java.lang.RuntimeException: error,check your partitionScope definition.at io.mycat.route.util.PartitionUtil.<init>(PartitionUtil.java:69) PartitionUtil.java 注意:其中count,l......

Danni3
16分钟前
6
0
OSChina 周三乱弹 —— 魂淡!不是这种粪发涂墙

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @小小编辑推荐歌曲《10/10》- Rex Orange County 《10/10》- Rex Orange County 手机党少年们想听歌,请使劲儿戳(这里) @奋斗的小牛 :上午...

小小编辑
29分钟前
544
7
Arduino教程:认识Arduino控制板

@toc 1.1 课程说明 认识Arduino控制板的各个部分, 1.2 器材 名称 数量 规格 Arduino uno控制板 1 R3 1.3 UNO电路: UNO参数 名称 参数说明 工作电压: 5V 输入电压: 接上USB时无须外部供电...

acktomas
35分钟前
6
0
WeUI框架

WeUI框架 WeUI是一套小程序的UI框架,所谓UI框架就是一套界面设计方案,有了组件,我们可以用它来拼接出一个内容丰富的小程序,而有了UI框架,我们就可以让我们的小程序变得更加美观。 体验W...

达达前端小酒馆
38分钟前
3
0
Rainbond 5.1.8发布,应用网关支持多IP网络接入

2019年10月23日,Rainbond发布5.1.8版本,本次版本更新带来了应用网关对多IP的支持, 第三方组件对域名实例的支持 等新功能和修复若干BUG。 Rainbond:支撑企业应用的开发、架构、交付和运维的...

好雨云帮
39分钟前
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部