Java基础总结一

JVM、JRE、JDK

JVM:Java Virtual  Mechine(Java虚拟机)

JVM是Java实现跨平台的最核心部分。 JVM是一个标准,一套规范,规定了.class文件在其内部运行的相关标准和规范。 及其相关的内部构成。所有的java程序首先会被编译成.class的类文件,这种类文件可以在虚拟机上运行。即.class文件不直接与机器的操作系统相互关联,而是经过虚拟机间接与操作系统进行交互。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成能够在JVM上运行的.class(字节码文件),就可以在多种平台上不加修改的运行。

JRE:Java Runtime Environment(Java运行环境)

JRE是Java必须的运行环境,包括JVM和Java核心类库。Java Runtime Environment(JRE)是可以在其上运行、测试和传输应用程序的Java平台。它包括Java虚拟机(JVM)、Java核心类库和支持文件,它不包含开发工具(JDK)--编译器、调试器和其他工具。JRE需要辅助软件Java Plug-in 以便在浏览器中运行applet。即如果想要运行一个开发好的Java程序,只需要安装JRE和相关的一些插件就可以。

JDK:Java Development Kit(Java开发工具包)

JDK是提供给开发人员使用的工具包,里面包含了JRE和开发工具(如编译工具Javac.exe、打包工具jar.exe等)。在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin和lib。在这里可认为bin里的就是jvm,lib中则是jvm工作所需要的库类,而jvm和lib合起来成为jre。

对于以上三者的范围:JDK>JRE>JVM


初探JVM原理:

在初步学习完Java的语言基础后,自己在网上也看到了许多关于JVM底层原理的文章和博客等,对网上其他博主所说的学习Java需要学习JVM原理这一个观点表示一致认同。所以自己又回过头,参考各种途径的资料,对JVM原理有个基本的学习和认识,于是记录下来,以便自己日后温习。

  • Java=技术!=编程语言

Java不仅仅是片面上理解的Java编程语言,Java实际上是一种技术。它由四方面组成:Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。

  • .Java程序如何跑起来

.Java文件通过Java编译器编译成字节码(.class文件),然后字节码被装入内存中,一旦字节码进入虚拟机,它就会被解释器解释执行或者是被即时代码发生器有选择的转换成机器码执行。JVM在它的生命周期中有一个明确的任务,就是运行Java程序,当Java程序启动的时候,就会产生JVM的一个实例,当程序运行结束的时候,该实例也跟着消失了。所谓的Java平台,实际上由Java虚拟机和Java应用程序接口搭建,Java语言仅仅是进入这个平台的通道。用Java语言编写并编译的程序可以运行在这个平台上。在Java平台的结构中,可以看出JVM处在核心的位置,是程序与底层操作系统和硬件无关的关键所在。它的下方是移植接口,该接口两部分组成:适配器和Java操作系统,其中依赖于平台的部分称为适配器(适配windows、linux等操作系统)。JVM通过移植接口在具体的平台和操作系统上实现。在JVM上方是Java的基本库类和扩展库类以及他们的API,利用Java API编写得应用程序(application)和小程序(Java applet)可以在任何Java平台上运行而无需考虑地城平台,就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而保证了Java的与平台无关性。

小结:Java源文件--编译器--字节码文件--JVM--机器码

  • 类的加载机制

当程序需要使用某个类的时候,如果该类还未加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类的初始化。

(1)加载:指的是.class文件读入内存,并为之创建一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意:这里的加载不一定非要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其他文件生成(比如将jsp文件转换成对应的Class类)

(2)连接:分为验证、准备、解析三步。验证主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机本身。即确保类内部结构准确且与他类协调一致。准备:负责为类的静态成员分配内存,并设置默认的初始值。

public static final int H=1000

实际上变量H在准备阶段过后的初始值为0而不是1000,将H赋值为1000的putstatic指令是程序被编译后,存放于类构造器<client>方法之中。

解析:是指虚拟机将常量池中的符号号引用替换为直接引用的过程。所谓符号引用就是class文件中的:CONSTANT_Class_info、CONSTANT_Field_info等类型的常量,该引用的目标不一定要已经加载到内存中。虽然各种虚拟机实现的内存布局可能不同,但是他们能接受的符号引用必须是一致的。因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。不同于直接引用的是,直接引用可以是指向目标的指针、相对偏移量或是一个能间接定位到的目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中。即解析的作用,是保证所有的字符引用都变为直接引用,所有的引用目标都存在于内存中。

(3)初始化:初始化阶段是类加载最后的一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其他操作都是由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的复制操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。Ps:如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。

注意:一下几种情况不会执行类初始化:

(1)通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

(2)定义对象数组,不会触发类的初始化。

(3)常量在编译期间会存入调用类的常量池中,本质上没有直接引用定义常量的类,不会触发定义常量所在的类。

(4)通过类名获取Class对象,不会触发类的初始化。

(5)通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化

(6)通过ClassLOader默认的loadClass方法,也不会触发初始化动作。

那么有哪些情况会初始化呢?

(1)创建类的实例。

(2)访问类的讲台变量或者为静态变量赋值。

(3)调用类的静态方法。

(4)使用类的反射方式来强制创建某个类或接口对应的java.lang.Class对象

(5)初始化摸个类的子类。

(6)直接使用java.exe命令来运行某个主类(包含main()方法的那个类),虚拟机先初始化这个主类。

(7)只有以上方式会出发初始化,也成为一个类进行主动引用,除此以外,所有其他方式都不会出发初始化,也成为被动引用。

类加载器

Bootstrap ClassLoader

根类加载器,也称被引导加载器,负责java核心类的加载:比如System\String等。在JDK的JRE的lib目录下rt.jar文件中。

Extension ClassLoader扩展类加载器

负责JRE的扩展目录总jar包的加载,在JDK中JRE的lib目录下ext目录中。

System ClassLoader系统类加载器

负责在JVM启动的时候加载来自java命令的class文件以及classpath环境变量所指定的jar包和类路径。

在java中,类加载采用的是代理模式。所谓的代理模式,可以简单的理解为,看起来是这个类加载器进行加载,但其实并不是这个加载器进行加载。这就是代理模式。在代理模式中,有一个比较重要的一种是双亲委托模式。所谓的双亲委托模式就是:比如说,我定义了一个类A,在加载类A的时候,首先由自定义的类加载器,再到应用程序的类加载器,再到拓展的类加载器,最后到最高级的加载器。首先是由最高级的加载器进行判断,它是否能够加载这个类,如果能够加载的话,就进行加载。如果不能够进行加载的话,再接下来看一下额外的类加载器能否进行加载。这样一层一层地往下。如果都不能加载的话,那么就会出现错误。这是要注意的。 双亲委托机制的最大的好处就是可以确保是安全的。

JVM数据运行区(栈管运行,堆管存储)

说明:JVM调优主要是优化Heap堆和Method Area方法区

(1)Native Method Stack本地方法栈

它的具体做法是Native Method Stack中登记native方法,在Execution Engine(执行引擎)执行时加载native libraies。

(2)PC Register程序计数器

每个线程都有一个程序计算器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

(3)Method Area方法区

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。静态变量+常量+类信息+运行时常量池存在方法区中,实例变量存在堆内存中。

(4)Stack栈

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。

栈帧中主要保存3类数据:

本地变量(Local Variables):输入参数和输出参数以及方法内的变量;

栈操作(Operand Stack):记录出栈、入栈的操作;

栈帧数据(Frame Data):包括类文件、方法等。

栈运行的原理:

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,B方法又调用了C方法,于是产生栈帧F3也被压入栈…… 依次执行完毕后,先弹出后进......F3栈帧,再弹出F2栈帧,再弹出F1栈帧。遵循“先进后出”/“后进先出”原则。

(5)Heap堆

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

① 新生区

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个:0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园进行垃圾回收(Minor GC),将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1去也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生Major GC(FullGCC),进行养老区的内存清理。若养老区执行Full GC 之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

a.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。

b.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

② 养老区

养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。

③ 永久区

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。


注:该文章仅个人学习过程总结,若有不当之处,望不吝赐教。更多参考https://baijiahao.baidu.com/s?id=1606480770208000096&wfr=spider&for=pchttp://www.importnew.com/25295.html以及自行查问度娘。

猜你喜欢

转载自blog.csdn.net/m0_37265215/article/details/81327576