JRE、JDK和JVM的关系
JRE(Java Runtime Environment, Java运行环境)是Java平台,所有的程序都要在JRE下才能够运行。包括JVM和Java核心类库和支持文件。
JDK(Java Development Kit,Java开发工具包)是用来编译、调试Java程序的开发工具包。包括Java工具(javac/java/jdb等)和Java基础的类库(java API )。
JVM(Java Virtual Machine, Java虚拟机)是JRE的一部分。JVM主要工作是解释自己的指令集(即字节码)并映射到本地的CPU指令集和OS的系统调用。Java语言是跨平台运行的,不同的操作系统会有不同的JVM映射规则,使之与操作系统无关,完成跨平台性
JVM基本的介绍
类加载器
如果 JVM 想要执行某个.class的 文件,我们需要将其装进一个 类加载器中,它就像一个搬运工一样,会把所有的 .class 文件全部搬进JVM里面来,搬运如下图所示
方法区
是用于存放类似于元数据信息方面的数据的,比如类信息,常量,静态变量,编译后代码,类相关的信息···等
类加载器将 .class 文件搬过来就是先丢到这一块上,并且会由字节码执行引擎前途执行该个class文件
堆
堆 主要放了一些存储的数据,比如对象实例,数组···等,它和方法区都同属于 线程共享区域 。也就是说它们都是 线程不安全 的
1:一般来说我们new出来的对象一般情况下都是会放到了Eden园区里面的
2:一般来说对象如果经理了一次gc之后,如果还没有被干掉的话,那么它的分代年龄就会加一,以此类推逐渐这样下去。并且就会在survivor区里面复制过来复制过去,一直这样下去,只到分代年龄达到了15之后就会被分到了老年代里面,同时一些大对象,一些长期存活的对象,等都会被分到老年代里面去。分代年龄是记录到了一个对象的对象头里面
栈
栈 这是我们的代码运行空间。我们编写的每一个方法都会放到 栈 里面运行。
我们会听说过 本地方法栈 或者 本地方法接口 这两个名词,不过我们基本不会涉及这两块的内容,它俩底层是使用C来进行工作的,和Java没有太大的关系。每个线程独有的东西,可以理解为线程栈。线程栈里面有一个栈帧,作用是当线程开始运行的时候,jvm虚拟机会给该个线程的每个方法分配一个专属的内存空间用来存储自己独有的变量
栈帧
栈帧里面又会有局部变量表,操作数栈,动态链接,方法出口
局部变量表:存放局部变量
操作数栈:存放局部变量
实例,
class Math { public Math() {} public int compute() { int a=1; int b=2; int c=(a+b)*10; return c; } } //通过反编译后得到的代码如下所示 Compiled from "Math_test.java" class com.jvm.Math { public com.jvm.Math(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public int compute(); Code: 0: iconst_1 //将int类型的常量1压到操作数栈 1: istore_1 //将int类型的值存入局部变量1,给我们的局部变量a在局部变量表里面分配内存空间,并且将1给它 2: iconst_2 //和上述同样的道理 3: istore_2 //和上述同样的道理 4: iload_1 //从局部变量1装在int类型的数值,加载到操作数栈 5: iload_2 //从局部变量2装在int类型的数值 6: iadd //执行加法,需要从操作数栈里面弹出两个数,执行操作,并且得到的结果重新压入栈 7: bipush 10 //将10推入操作数栈 9: imul //执行乘法 10: istore_3 //存储 11: iload_3 // 12: ireturn //返回 }
程序计数器
主要就是完成一个加载工作,类似于一个指针一样的,指向下一行我们需要执行的代码。和栈一样,都是 线程独享 的,就是说每一个线程都会有自己对应的一块区域而不会存在并发和多线程的问题。程序计数器的数值,每运行一个代码都会被改变
类加载器的介绍
1:一个类,从加载到内存虚拟机当中,到运行结束被释放总共会经历七个阶段,分别是:加载,验证,准备,解析,初始化,使用,卸载,其中验证,准备,解析又称为连接
加载:
加载时候主要运行三个步骤,将class文件加载到内存区,将静态数据结构转化成为方法区当中运行的数据结构,在堆当中生成代表这个类的java.lang.class对象作为数据访问的入口
链接:
验证:实际上就是一个简单的安全验证
准备:为static修饰的变量在方法区当中分配内存空间,并且对其进行赋予默认数值,但是不包括实例变量,在堆当中
解析:虚拟机将常量池内的符号引用替换为直接引用的过程
初始化
初始化其实就是执行类构造器方法的<clinit>()的过程,而且要保证执行前父类的<clinit>()方法执行完毕。这个方法由编译器收集,顺序执行所有类变量(static修饰的成员变量)显式初始化和静态代码块中语句。此时准备阶段时的那个 static int a 由默认初始化的0变成了显式初始化的3。 由于执行顺序缘故,初始化阶段类变量如果在静态代码块中又进行了更改,会覆盖类变量的显式初始化,最终值会为静态代码块中的赋值。
卸载
GC将无用对象从内存中卸载
本地方法
如果说,线程在运行的过程当中用到了本地方法的话,那么就会在栈里面分配一个本地方法栈,的空间,用来存储本地方法所需要的东西
JVM分析实例
测试代码如下所示,如下代码,表示的是一直会在Eden里面new出很多对象,但是该对象是不会被销毁的,所以会看到刚开始的对象被放到Eden里面,之后又被放到了s0,然后又被放到了s1,到最后会被放到老年区
public class JavaTest { byte[] a= new byte[1024 * 100]; public static void main(String[] args) throws InterruptedException { ArrayList<HeapTest> heapTests=new ArrayList<>(); while(true) { heapTests.add(new HeapTest()); Thread.sleep(5); } } }
为什么要有stw的机制
1:复制过程中,对象位置发生变化,若应用程序线程同步执行,为保证正常,复杂度需要多高。
2:如果说GC和用户程序并行,可能会导致将存活的对象看做未存活
JVM对象的引用分为了四种类型:
强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用时, GC时才会被回收)
软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有内存不够的情况下才会被GC)
弱引用:在GC时一定会被GC回收。
虚引用:虚引用只是用来得知对象是否被GC。
JVM类加载顺序
1.Booststrap ClassLoader
跟ClassLoader一样,底层用C++实现,JVM启动时初始化此ClassLoader,并由此完成$JAVA_HONE中jre/lib/rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。
2.Extension ClassLoader
JVM用此classloader来加载扩展功能的一些jar包
3.System ClassLoader
JVM用此ClassLoader来加载启动参数中指定的ClassPath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。
4.User-Defined ClassLoader
User-Defined ClassLoader是Java开发人员继承ClassLoader抽象类实现的ClassLoader,基于自定义的ClassLoader可用于加载非ClassPath中的jar以及目录。
小知识
java当中的一个对象的大小就是一个对象的里面的所有的属性的大小之和,比如说int类型占用了四个字节,String类型占用了八个字节,等等,全部加起来,便是可以了
jvm一般在如下情况会出发GC操作
触发MinorGC(Young GC)
虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间
1、如果大于的话,直接执行minorGC
2、如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC
3、如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升的大小,如果小于直接执行FullGC
4、如果大于的话,执行minorGC
触发FullGC
- 老年代空间不足
如果创建一个大对象,Eden区域当中放不下这个大对象,会直接保存在老年代当中,如果老年代空间也不足,就会触发Full GC。为了避免这种情况,最好就是不要创建太大的对象。
- 持久代空间不足
如果有持久代空间的话,系统当中需要加载的类,调用的方法很多,同时持久代当中没有足够的空间,就出触发一次Full GC
- YGC出现promotion failure
promotion failure发生在Young GC, 如果Survivor区当中存活对象的年龄达到了设定值,会就将Survivor区当中的对象拷贝到老年代,如果老年代的空间不足,就会发生promotion failure, 接下去就会发生Full GC.
- 统计YGC发生时晋升到老年代的平均总大小大于老年代的空闲空间
在发生YGC是会判断,是否安全,这里的安全指的是,当前老年代空间可以容纳YGC晋升的对象的平均大小,如果不安全,就不会执行YGC,转而执行Full GC。