3.2.JVM

概念划分

学习JVM是如何从入门到放弃的?学习JVM是如何从入门到放弃的? - 简书

了解JVM的结构,【JVM故事】了解JVM的结构,好在面试时吹牛

1.3万字,13图,JVM史上最最最最完整深度解析:

1.3 万字 13 图!JVM 史上最最最完整深入解析

JVM区域划分

内存结构

1.[JVM系列4]new object()到底占用几个字节,看完这篇就彻底明白了:

【JVM系列4】new Object()到底占用几个字节,看完这篇就彻底明白了_new object占用多少字节_双子孤狼的博客-CSDN博客

(1)单个线程私有

a.虚拟机栈

虚拟机栈描述的是Java方法执行的内存结构,每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接和方法出口信息等,每个方法的执行就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

Java-JVM栈帧(Stack Frame):

https://www.cnblogs.com/jhxxb/p/11001238.html

b.本地方法栈

本地方法栈与虚拟机栈所发挥的作用极其类似,区别只是虚拟机栈是为虚拟机执行的java方法服务,而本地防范栈则为虚拟机使用到的Native方法服务。

c.程序计数器

当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变该计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等基础功能。

JVM内存结构-程序计数器:

https://www.cnblogs.com/manayi/p/9290490.html

JVM刨根问底之程序计数器:

JVM刨根问底之程序计数器_jvm程序计数器原理_大道化简的博客-CSDN博客

(2)所有线程共享

a.堆

堆在虚拟机启动时创建,几乎所有的对象示例和数组都是在堆上进行内存分配。

JVM堆栈的理解:

JVM堆栈的理解_jvm栈堆_坤少的IT之路的博客-CSDN博客

JVM结构及堆的划分:

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

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态扩展(当前大部分的Java虚拟机都可以动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展前无法申请到足够的内存,就会抛出OutOfMemoryError异常。

b.方法区

用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区包括Java的运行时常量池。(保存数据包括类型信息、类型的常量池、字段信息、方法信息、类变量、指向类加载器的引用、指向Class实例的引用、方法表。)

Java虚拟机-运行时常量池:

https://www.cnblogs.com/xiaotian15/p/6971353.html

Java常量池(静态常量池与运行时常量池):

常量池的优点:其实现了对象共享,避免频繁的创建和销毁对象而影响性能;节省运行时间,==比equals()方法更快。

字面量是用于表达源代码中一个固定值的表示法,几乎所有计算机编程语言都具有基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型的值也支持字面量表示,还有一些甚至对枚举类型的元素以及数组、记录和对象等复合类型的值也支持字面量表示法。

字面量与常量的区别:字面量是没有用标识封装起来的量,是指的原始状态。例如,const int A=1;A是常量,1是字面量。

关于JVM的运行时常量池究竟在哪里?

jdk1.7之前,常量池放在永久代中;jdk1.7时,常量池从永久代移到了堆中;jdk1.8时,移除了永久代,方法区移到堆外内存,即本地内存中,而常量池依然在堆中。字符串常量池在堆中,基本类型数据以及引用数据放在方法区中。

JVM中常量池存在在哪里?

JVM中常量池存放在哪里 - 简书

基本数据的储存位置

在方法中声明(局部变量):调用方式时在栈中分配空间给变量,结束时释放栈,体现变量的局限性。

在类中声明(成员变量):由于作用周期需要,变量存放在堆中。不会因方法销毁为失效,类似于C中的全局变量。

对于引用变量来说,对应内存所储存的值是一个引用,是对象的储存地址,被存放在栈中。

对象创建过程

new一个对象发生了什么:

今日头条面试官竟然问我new一个对象背后发生了什么?这太难了...

a.介绍:

Java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就先通过类的全限定名来加载。

加载并初始化类完成后,再进行对象的创建工作,第一次使用的类,new一个对象就可以分为两个过程:加载并初始化类和创建对象。

b.类加载过程

同上JVM的类加载机制和类加载器。

c.创建对象

在堆区分配对象需要的内存:分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量。

对所有实例变量赋默认值:将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值。

执行实例初始化代码:初始化顺序是先初始化父类在初始化子类,初始化时先执行实例代码然后是构造方法。

d.补充

通过实例引用调用实例方法的时候,先从方法区中对象的实际类型信息找,找不到的话再去父类类型信息中找。

如果继承的层次比较深,要调用的方法位于比较上层的父类,则调用的效率是比较低的,因为每次调用都要经过很多次查找,这时候大多系统会采用一种称为虚方法表的方法来优化调用的效率。

所谓虚方法表,就是在类加载的时候,为每个类创建一个表,这个表包括该类的对象所有动态绑定的方法及其地址,包括父类的方法,但一个方法只有一条记录,子类重写了父类方法后只会保留了子类的方法。当通过对象动态绑定方法的时候,只需要查找这个表就可以了,而不需要挨个查找每个父类。

JVM调优

一分钟带你入门JVM性能调优:

性能调优-------(三)1分钟带你入门JVM性能调优_jvm的内存调优_小诚信驿站的博客-CSDN博客

从实际案例聊聊Java应用的GC优化:

https://www.cnblogs.com/feiyudemeng/p/8276911.html

确认JVM各个区域比例大小的经验和准则:

确认JVM各个区域比例大小的经验和准则

JVM参数MetaspaceSize的误解:

https://www.cnblogs.com/williamjie/p/9558136.html

一次线上JVM调优实践,Full GC 40次/天到10天一次的优化过程:

一次线上 JVM 调优实践,FullGC 40 次/天 到10 天一次的优化过程

JVM参数:

-Xms:设置堆的初始值,堆内存越大,越不容易发生full GC,内存越小,垃圾回收越频繁,增加垃圾回收总时间,降低吞吐量

-Xmx:设置堆的最大值

-Xmn:设置新生代的大小

-XX:NewSize设置新生代的大小

-XX:NewRation设置老年代和新生代的比例

-XX:SurvivorRatio设置新生代中eden和survivor的比例

-XX:TargetSurvivorRatio 设置survivor区的可使用率,达到该使用率,被送到老年代。

CPU高占用

Java实践总结:高CPU占用问题:

Java实践总结:高CPU占用问题_机器熊技术大杂烩的博客-CSDN博客

Linux排查Java程序占用CPU很高的解决办法:

https://www.cnblogs.com/MikeYao/p/10485166.html

JMM(内存模型)

①博客:

java内存模型以及happend-before规则:

java内存模型以及happens-before规则 - 简书

深入理解Java内存模型:深入理解Java内存模型 - 简书

史上最清晰的Java内存模型介绍:史上最清晰的Java内存模型介绍_最近比较烦。的博客-CSDN博客

你了解Java内存结构嘛?

【Java千问】你了解Java内存结构么(Java7、8、9内存结构的区别)_老莫8的博客-CSDN博客

求你了,别再问Java内存模型的时候给我讲堆栈方法区了:

求你了,再问你Java内存模型的时候别再给我讲堆栈方法区了…

面试官想到,一个volatile,敖丙都能吹半小时:

面试官想到,一个Volatile,敖丙都能吹半小时

Java内存模型(JMM)总结:

Java内存模型(JMM)总结 - 知乎

②介绍:

JMM是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制和规范。目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器对代码指令的重排序、处理器对代码乱序执行等问题。

类加载机制

1.博客

jvm之java类加载机制和类加载器(ClassLoader)的详解_超级战斗王的博客-CSDN博客

Java类加载(类的生命周期):

https://www.cnblogs.com/jhxxb/p/10900405.html

Java类加载机制,你理解了嘛:

百度安全验证

外婆问我:“什么是双亲委派原则”:

外婆问我:什么是双亲委派原则?

2.什么是类加载:

类的加载是指将类的.class文件中的二进制数据读入内存,将其放到运行时数据区大的方法区中,然后在堆中创建一个Java.lang.Class对象,用来封装在方法区内的数据结构。

3.什么时候启动类加载:

类加载器并不需要等到某个类被首次使用时才加载它,JVM规范允许类加载器在预料到某个类将要被使用时而预先加载它,如果在预先加载的过程中遇到.class文件缺失或存在错误,类加载器必须在首次使用该类时才报告错误,如果这个类一直没有被使用,那么类加载器就不会报告错误。

4.类加载器从哪里加载.class文件:

本地磁盘

网上加载.class文件(Applet)

从数据库中

压缩文件中(ZAR,jar等)

从其他文件生成的(JSP应用)

5.类加载步骤:

a.加载:

加载指的是类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中加载任何类时,系统都会为之创建一个java.lang.Class对象。类的加载由类的加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器,除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同的来源加载类的二进制数据,通常有如下几种来源:

从本地文件系统加载class文件,这是绝大多数示例程序的二进制方式。

从Jar包加载class文件,这种方式也是常见的,JDBC编程时用到的数据库驱动就是放在jar文件中的,JVM可以从jar文件中直接加载该class文件。

通过网络加载class文件。

把一个Java源文件动态编译,并执行加载。

类加载器通常无需等到首次使用该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

b.连接

当类被加载之后,系统为之产生一个对应的Class对象,接着将会进入链接阶段,链接阶段负责把类的二进制文件合并到jre中,类链接又可分为如下三个阶段。

验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致,Java语言是相对安全的语言,例如它有C++不具有的数组越界的检查,这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一种重要的防线,越是严谨的验证机制越安全,验证的目的在于确保Class文件的字节流中包含的信息是否符合当前的虚拟机的要求,不会危害虚拟机自身安全。主要包含四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如,主次版本号是否在当前虚拟机处理的范围之内;常量池中是否有不被支持的常量类型;指向常量中的索引值是否存在不存在的常量和不符合类型的常量。

元数据验证:对字节码描述的信息进行语义的分析,分析是否符合Java语言语法的规范。

字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要为针对元数据验证后对方法体的验证。保证方法在运行时不会有危害出现。

符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要是去确定访问类型等涉及到引用的情况,保证引用一定会被访问到,不会出现类无法访问的问题。

准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值。

解析:将类的二进制数据中的符号引用替换成直接引用。符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何字面形式的自变量,只要不出现冲突能够定位到改变量即可。直接引用:指向目标的指针,偏移量或者能够直接定位的句柄,该引用是和内存中的布局有关,并且一定能被加载进来。

c.初始化:

初始化是为类的静态变量赋予正确的初始值。

6.Java语言自带的加载器有哪些:

启动类加载器(BootStrap ClassLoader):负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类。

扩展类加载器(ExtClassLoader):负责加载Java平台的扩展功能的jar包,包括$JAVA_HOME中的jre/lib/ext/*.jar或者-D java .ext.dirs指定目录下的jar包。

应用类加载器(AppClassLoader):负责记载classpath中指定的jar包及目录中的的class。

双亲委派模型

执行过程:

当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类Extension ClassLoader去完成。

当ExtClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给BootStrap ClassLoader去完成。

如果BootStrap ClassLoader加载失败,(例如在$JAVA_HOME/jre/lib里未查到该class),会使用ExtClassLoader来尝试加载。

如果Extension ClassLoader也加载失败,则会使用App ClassLoader来加载。

如果AppClassLoader也加载失败,则会抛出ClassNotFoundException。

特点:如果一个类加载器接收到类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。类加载器在成功加载某个类之后,会把得到的java.lang.class缓存起来,下次在尝试请求加载这个类,类加载器会使用缓存的类的实例,而不会尝试再次加载。

好处:防止内存中出现多份同样的字节码(安全性角度)。

指令重排序

happens-before

来源:为了加快程序的执行进度,不管是编译器,CPU还是内存,都会存在对程序指令的重排序操作,但是指令重排序需要遵循happends-before规则

happens-before原则定义如下:

如果一个操作happens-before(之前发生)另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前;两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

happens-before八大规则如下:

  1. (线程)启动规则:Thread对象的start()方法先行发生于此线程的每一个动作;
  2. (线程)中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  3. (线程)终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止执行;
  4. (对象)终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。
  5. 次序规则:一个线程内,按照代码顺序,书写在前面的操作先行于书写在后面的操作;
  6. 锁定规则:一个unlock锁先行发生于后面对同一个锁的lock操作;
  7. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
  8. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生与操作C,则可以得出操作A先行发生于操作C。

直接内存(Direct Buffer)

1.Java堆外内存之三:堆外内存回收方法:

https://www.cnblogs.com/duanxz/p/6089485.html

java直接内存为什么快——直接内存与JVM源码分析:

java直接内存为什么快_直接内存与 JVM 源码分析_洗心岛的博客-CSDN博客

好处:

减少了垃圾回收的工作,因为垃圾回收会停顿用户线程的工作。

加快了复制的速度,因为堆内在flush到远程时,会先复制到直接内存,然后在发送,而堆外内存相当于省掉了这个环节。

可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。

可以扩展至更大的内存空间。

缺点:

直接内存难以控制,如果内存泄漏,那么难以排查。

直接内存相对来说,不适合存储很复杂的对象,一般简单的对象更适合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值