JVM概论

JVM的平台无关性与性能

问题:Compile once,Run anywhere如何实现

Java源码首先被编译成字节码,再由不同平台的JVM进行解析,JAVA语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。

b03

为什么JVM不直接将源码进行编译成机器码去执行

(1)准备工作太过繁琐

JVM每次进行编译的时候都会对源代码进行各种检查,纠错

(2)兼容性

JVM不仅仅可以给java语言编译成的class文件进行解释,还可以对任何语言,只要是解释为.class字节码都可以解释

java与C++的区别

  • 都是面向对象的语言,都支持封装、继承和多态
  • Java不提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
  • Java有自动内存管理机制,不需要程序员手动释放无用内存
  • java编译出字节码文件,在虚拟机上运行;C编译出二进制码文件,直接在操作系统上运行。

java运行机制对性能的影响

java确实是编译型语言,但也有解释部分,通过JVM保证数据的一致性,也确保Java的平台无关性;java中有JIT编译器,将我们编写好的源码编译成字节码,然后再装载到JRE,JRE再对字节码进行解析。

b04

以下我们来比较以下C代码与java代码的执行效率

#include<stdio.h>

int main() {
    printf("Hello World!");
}

b04a

再来看看Java代码

  1.    public class Main {
         public static void main(String[] args) {
                  System.out.prinln("Hello World!");
         }
       }
    

    b04b

python的性能比java更慢

python特性, 强类型、动态类型检查的语言。所谓动态类型,是指在定义变量时,我们无需指定变量的类型,Python 解释器会在运行时自动检查。 (2017年发布的python 3.5以上加入类型检查机制,通过特定注解完成,但是还是跟原生强类型检查的java、C没法比,最新版phthon 是 10月发布的3.7.5和8月发布的3.6.9)

没有;作为语句结尾标识,用换行符作为语句结束标志,很长的语句需要用\来把下一行作为本行的续行

total = item_one + \
        item_two + \
        item_three

没有{}作为语句块的标识,全凭严格的缩进来组织。

flag = False
name = 'luren'
if name == 'python':         # 判断变量是否为 python
    flag = True              # 条件成立时设置标志为真
    print 'welcome boss'     # 并输出欢迎信息
else:
    print name               # 条件不成立时输出变量名称 

类加载机制

java虚拟机结构

问题:JVM由哪几块组成?

由类加载器、内存空间、执行引擎,垃圾收集、本地方法接口组成。

b04c

img

英文名称: 类加载器 Class Loader

内存空间或运行时内存空间 Runtime Data Area

栈:Stack

堆:Heap

Java的内存分为两类,一类是栈内存,一类是堆内存

栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。

堆是与栈作用不同的内存,一般用于存放,不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。

  1. 栈是用来存放基本类型的变量和引用类型的变量名(用来指向在堆内存中的引用变量),堆用来存放new出来的对象和数组(引用变量)。

  2. 栈的存取速度快,但不灵活。堆的存取速度慢,但是存取灵活,空间动态分配。

  3. 栈在建立在连续的物理位置上,而堆只需要逻辑位置连续即可。

  4. 堆是JVM运行时最大的内存区域。

stack(栈)的空间由操作系统自动分配和释放,heap(堆)的空间是手动申请和释放的,heap常用new关键字来分配。

stack空间有限,heap的空间是很大的自由区。

在Java中,

若只是声明一个对象,则先在栈内存中为其分配地址空间,

若再new一下,实例化它,则在堆内存中为其分配地址。

方法区:Method Area

本地方法栈:Native Method Stack

执行引擎:Execution Engine

本地方法接口:Native Interface

类的生命周期

问题:java类的生命周期由几个阶段组成

b04d

类加载机制主要就是加载、验证、准备、解析、初始化这些过程

1、类加载过程

问题:类加载(Loading)的过程中做了哪些处理

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将获取到的二进制字节流转化成一种数据结构并放进方法区
  • 在内存中生成一个代表此类的java.lang.Class对象,作为访问方法区中各种数据的接口

被加载的类的信息存储在方法区中,可以被线程所共享,也就是说,加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在了方法区之中。 Class对象虽然是在内存中,但并未明确规定是在Java堆中,对于HotSpot来说,Class对象存储在方法区中。它作为程序访问方法区中二进制字节流中所存储各种数据的接口。

2、验证阶段

这一阶段的目的主要是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,从而不会危害虚拟机自身安全。也就是说,当加载阶段将字节流加载进方法区之后,JVM需要做的第一件事就是对字节流进行安全校验,以保证格式正确,使自己之后能正确的解析到数据并保证这些数据不会对自身造成危害。

验证阶段主要分成四个子阶段:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

3、准备阶段

1.准备阶段的目的:正式**为类变量分配内存并设置类变量初始值****的阶段,这些变量所使用的内存将在方法区中分配。

注意我的重点:是类变量(static)不是实例变量,还有,我们又知道了在JVM的方法区中不仅存储着Class字节流(按照运行时方法区的数据结构进行存储,上述的二进制字节流是不严谨的说法,只是为了大家好理解),还有我们的类变量。

2.这里的类变量初始值通常是指数据类型的零值。比如int的零值为0,long为0L,boolean为false… …真正的初始化赋值是在初始化阶段进行的。

额外一点,如果你设置的类变量还具有final字段,如下:

public static final int value = 123;

那么在准备阶段变量的初始值就会被直接初始化为123,具体原因是由于拥有final字段的变量在它的字段属性表中会出现ConstantValue属性。

4、解析阶段

解析阶段的目的:虚拟机将常量池内的符号引用替换为直接引用

常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

符号引用:总的来说就是常量池中存储的那些描述类、方法、接口的字面量,你可以简单的理解为就是那些所需要信息的全限定名,目的就是为了虚拟机在使用的时候可以定位到所需要的目标。

直接引用:直接指向目标的指针、相对偏移量或能间接定位到目标的句柄。

重新解读:虚拟机将运行时常量池中那些仅代表其他信息的符号引用解析为直接指向所需信息所在地址的指针。

  • 在解析阶段主要有以下不同的动作

    • 类或接口的解析(注意数组类和非数组类)
    • 字段(简单名称+字段描述符)解析(注意递归搜索)
    • 类方法解析(注意递归搜索)
    • 接口方法解析(注意递归搜索)

动态连接

大部分JVM的实现都是延迟加载或者叫做动态连接。它的意思就是JVM装载某个类A时,如果类A中有引用其他类B,虚拟机并不会将这个类B也同时装载进JVM内存,而是等到执行的时候才去装载。

而这个被引用的B类在引用它的类A中的表现形式主要被登记在了符号表中,而解析的过程就是当需要用到被引用类B的时候,将引用类B在引用类A的符号引用名改为内存里的直接引用。

5、对象初始化阶段

问题:给出 … 代码,会输出什么?

对象初始化过程,将按照类文件中初始化代码,依次执行。并创建出类文件的实例

虚拟机规范定义了5种情况,会触发类的初始化阶段,也正是这个阶段,JVM才真正开始执行类中定义的Java程序代码:

  • new一个对象、读取一个类静态字段、调用一个类的静态方法的时候
  • 对类进行反射调用的时候
  • 初始化一个类,发现父类还没有初始化,则先初始化父类
  • main方法开始执行时所在的类
  • 用动态语言支持时,如果一个java.lang.invoke.MethodHandle实例后解析结果REF_putStatic,REF_getStatic,REF_invokeStatic的方法句柄时,当该方法句柄对应的类没有初始化时,需要初始化该类 。

另外有三种引用类的方式不会触发初始化(也就是类的加载),为以下三种:

  • 通过子类(类名)引用父类的静态字段,不会导致子类初始化(会引发父类的初始化、不会引发子类的初始化)
  • 通过数组定义来引用类,不会触发此类的初始化
  • 引用另一个类中的常量不会触发另一个类的初始化,原因在于“常量传播优化

常量传播优化的例子

public class ConstClass {
    static {
        System.out.println("ConstClass init!");
    }
	public static final String HELLOWORLD = "hello world";
}

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD);
    }
}

常量“hello world”已经被存储到了NotInitialization类的常量池中,以后NotInitialization对常量ConstClass.HELLOWORLD的引用实际上都被转化为NotInitialization对自身常量池的引用。

通过数组定义来引用类的子类

 public class SuperClass {
     static {
         System.out.println("SuperClass init!");
     }
     public static int value = 123;
 } 

 public class NotInitialization {
     public static void main(String[] args) {
         SuperClass[] sca = new SuperClass[10];
     }
 }  

类加载器

需要了解、掌握的知识只有两点:

1.类加载器的命名空间
2.双亲委派模型

问题:java有几种类加载器

问题:类加载的双亲委派机制是什么

类加载器的命名空间:对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间。也就是说,你现在要比较两个类是否相等,只有在这两个类是同一个类加载器加载的前提下才有意义。

双亲委派模型:首先你得知道在JVM中有三种系统提供的类加载器:启动类加载器,扩展类加载器、应用程序类加载器

java的三种类加载器存在父子关系,子 加载器保存着附加在其的引用,当一个类加载器需要加载一个目标类时,会先委托父加载器去加载,然后父加载器会在自己的加载路径中搜索目标类,父加载器在自己的加载范围中找不到时,才会交给子加载器加载目标类。

采用双亲委托模式可以避免类加载混乱,而且还将类分层次了,例如java中lang包下的类在jvm启动时就被启动类加载器加载了,而用户一些代码类则由应用程序类加载器(AppClassLoader)加载,基于双亲委托模式,就算用户定义了与lang包中一样的类,最终还是由应用程序类加载器委托给启动类加载器去加载,这个时候启动类加载器发现已经加载过了lang包下的类了,所以两者都不会再重新加载。当然,如果使用者通过自定义的类加载器可以强行打破这种双亲委托模型,但也不会成功的,java安全管理器抛出将会抛出java.lang.SecurityException异常。

b04e

双亲委派模型的破坏者-线程上下文类加载器
在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。
线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

b04f


线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例
[外链图片转存中…(img-EHa3t4UG-1606728126629)]

原文地址:https://blog.csdn.net/weixin_43477950/article/details/110393033

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下:[root@admin ~]# jinfo --helpUsage: jinfo [option] &lt;pid&gt; (to connect to running process) jinfo [option] &lt;executable &lt;core&gt; (to connect to a core file) jinfo [option] [serve
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.htmlJava Virtual Machine Stacks,线程私有,生命周期与线程相同,描述的是Java方法执行的内存模型:每一个方法执行的同时都会创建一个栈帧(Stack Frame),由于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行就对应着栈帧在虚拟机栈中的入栈,出栈...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是由类加载器一步步解析, 执行与生成与内存区域中的; 并且jvm有自己的垃圾回收器对内存区域管理, 回收; 但是我们已经可以通过一些工具来在程序运行时查看对应的jvm内存使用情况, 帮助更好的分析与优化我们的代码;jps查看系统中有哪些java进程jps 命令类似与 linux 的 ps 命令,但是它只列出系统中所有的 Java 应用程序。 通过 jps 命令可以方便地查看 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模型是为了防止jdk核心类库被篡改,如果需要打破可以重写Classloader.loadClass方法。r 双亲委派模型:一个类加载器收到一个类的加载请求,他会先判断自身是否已存在该类,如果不存在上抛给上一级类加载器ClassLoad
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外内存,比如Netty,广泛使用了堆外内存,但是这部分的内存并不归JVM管理,GC算法并不会对它们进行回收,所以在使用堆外内存时,要格外小心,防止内存一直得不到释放,造成线上故障。堆外内存的申请和释放JDK的ByteBuffe
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?3.springmvc启动时组件的加载顺序?4.mybatis如何同时更新三条记录5.hibernate实现级联更新6.一个web程序应用程序启动时的加载流程7.如何向www.baidu.com地址发出请求时,并获取相应?8.???9.谈谈你对tcp/iptelnetudp协
堆设置-Xms256M:初始堆大小256M,默认为物理内存的1/64-Xmx1024M:最大堆大小1024M,默认为物理内存的1/4,等于与-XX:MaxHeapSize=64M-Xmn64M:年轻代大小为64M(JDK1.4后支持),相当于同时设置NewSize和MaxNewSize为64M-XX:NewSize=64M:初始年轻代大小-XX:MaxNewSize=256M:最大年轻代大小(默认
一.概述收集算法(JVM之垃圾回收-垃圾收集算法)是内存回收的抽象策略,垃圾收集器就是内存回收的具体实现。JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。就像没有最好的算法一样,垃圾收集器
Java中的堆是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: 在Java中,堆被划分成两个不同的区域:新生代(Young)、老年代(Old)。新生代(Young)又被划分为三个区域:Eden、S0、S1。 这样划分的目的是为了使JVM能够更好的管理堆内存中的对象,包
JVM深入理解JVM(4)——如何优化JavaGC「译」 PostedbyCrowonAugust21,2017本文翻译自SangminLee发表在Cubrid上的”BecomeaJavaGCExpert”系列文章的第三篇《HowtoTuneJavaGarbageCollection》,本文的作者是韩国人,写在JDK1.8发布之前,虽然有些地
 JVM深入理解JVM(2)——GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Scala等)程序员在提升开
运行时数据区  线程独有本地方法栈、虚拟机栈、程序计数器这些与线程对应的数据区会随着线程开始和结束创建和销毁  整体公有元数据区(又称方法区)、堆区会随着虚拟机启动而创建,随着虚拟机退出而销毁 
java整个堆大小设置:Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。永久代PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍年轻代Xmx的设置为老年代存活对象的1-1.5倍老年代的内存大小设置为老年代存活对象的2-3倍BTW: Sun官方建议年轻代
栈顶缓存(Top-of-StackCashing)技术基于栈式架构得虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派次数和内存读写次数 由于操作数是存储在内存重的,因此频繁地执行内存读/写操作必然影响速度。 综上
自用。同样的代码在不同的平台生成的机器码是不一样的,为什么java代码生成的字节码文件,能在不同的平台运行?因为不同版本的jdk里面的虚拟机会屏蔽不同操作系统在底层硬件与指令上的区别。栈:线程栈,局部变量存放栈内存区域。线程(分配一个栈)运行分配栈将局部变量放入内存。怎么放:栈
jconsole监控:1.java启动命令加上参数java-Djava.rmi.server.hostname=172.16.17.247-Dcom.sun.management.jmxremote-Dcom.sun.management.jmxremote.port=2099-Dcom.sun.management.jmxremote.authenticate=false-Dcom.sun.management.jmxremote.ssl=false -XX:+Unlock
类加载器分类publicclassStackStruTest{publicstaticvoidmain(String[]args){//对用户自定义个类来说:默认使用系统类加载器进行加载-----AppClassLoaderClassLoaderclassLoader=StackStruTest.class.getClassLoader();System.out.p
堆体系结构一个JVM实例只存在一个堆内存,堆内存的大小是可调节的。类加载器读取类文件后,需要把类、方法、常量、变量放在堆内存中,保存所有引用类型的真实信息,以方便执行器指向,堆内存分为三个部分:年轻代、老年代、永久代。Java7之前,堆内存在逻辑上分为:年轻代、老年代、永久代。物
JVM深入理解JVM(5)——虚拟机类加载机制 PostedbyCrowonAugust21,2017在Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能运行和使用。而虚拟机中,而虚拟机如何加载这些Class文件?Class文件中的信息进入到虚拟机中会发生什么变化?本文将逐步解答这
保存(持久化)对象及其状态到内存或者磁盘Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在