深入理解Java虚拟机

Why

  1. 深化技术能力,要有技术追求

  1. 解决线上问题

  1. 了解程序运行原理,优化代码,编写高质量代码

What

JVM是Java虚拟机,是用来执行Java字节码(二进制的形式)的虚拟机计算机, JVM作用在操作系统之上,而Java程序作用在jvm之上

How

Java内存模型

 

模块

存储内容

异常

唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

GC的主要管理区域,是JVM中最大一个块区域。

堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

Java虚拟机栈

每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口。

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,引用指针或句柄 )和returnAddress 类型。

线程私有,

生命周期与线程相同

如果线程请求的栈深度大

于虚拟机所允许的深度,将抛出StackOverflowError 异常;

(递归无收敛)

本地方法栈

保存的是本地方法要执行所需的必要参数

线程私有,

生命周期与线程相同

本地方法栈区域可能会抛出StackOverflowError 和OutOfMemoryError异常。其他参见Java虚拟机栈

方法区

(在jdk1.8之前叫方法区,1.8之后修改成元空间,不同点在于,方法区属于堆空间一部分,是有默认大小的,元空间利用的是宿主机的内存,是动态扩展的)

被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。

包含运行时常量池,编译期生成的各种字面量和符号引用,在类加载后存放到方法区的运行时常量池中。运行期间也可能将新的常量放入池中,比如String 类的intern() 方法。

当方法区无法满足内存分配需求时,将抛出

OutOfMemoryError 异常

程序计数器

每条线程的程序计数器,相互独立,线程私有的。

若线程正在执行Java 方法,则计数器记录正在执行的虚拟机字节

码指令的地址;若正在执行Natvie 方法,则计数器值(Undefined)

是唯一一个没有任何OutOfMemoryError 的区域

程序编译与加载

 

字节码

举个例子

java代码

 

编译后的字节码 javap -v


oker@okerdeMBP Documents % javap -v ByteCodeTest.class

Classfile /Users/oker/Documents/ByteCodeTest.class

Last modified 2022-10-5; size 405 bytes

MD5 checksum 522f1107f4fe2c07f2052e4b3387ff24

Compiled from "ByteCodeTest.java"

public class ByteCodeTest

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #6.#17 // java/lang/Object."<init>":()V

#2 = Fieldref #5.#18 // ByteCodeTest.i:I

#3 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;

#4 = Methodref #21.#22 // java/io/PrintStream.println:(I)V

#5 = Class #23 // ByteCodeTest

#6 = Class #24 // java/lang/Object

#7 = Utf8 i

#8 = Utf8 I

#9 = Utf8 <init>

#10 = Utf8 ()V

#11 = Utf8 Code

#12 = Utf8 LineNumberTable

#13 = Utf8 code

#14 = Utf8 (I)V

#15 = Utf8 SourceFile

#16 = Utf8 ByteCodeTest.java

#17 = NameAndType #9:#10 // "<init>":()V

#18 = NameAndType #7:#8 // i:I

#19 = Class #25 // java/lang/System

#20 = NameAndType #26:#27 // out:Ljava/io/PrintStream;

#21 = Class #28 // java/io/PrintStream

#22 = NameAndType #29:#14 // println:(I)V

#23 = Utf8 ByteCodeTest

#24 = Utf8 java/lang/Object

#25 = Utf8 java/lang/System

#26 = Utf8 out

#27 = Utf8 Ljava/io/PrintStream;

#28 = Utf8 java/io/PrintStream

#29 = Utf8 println

{

int i;

descriptor: I

flags:

public ByteCodeTest();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: aload_0

5: bipush 11

7: putfield #2 // Field i:I

10: return

LineNumberTable:

line 5: 0

line 7: 4

public void code(int);

descriptor: (I)V

flags: ACC_PUBLIC

Code:

stack=2, locals=2, args_size=2

0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;

3: iload_1

4: invokevirtual #4 // Method java/io/PrintStream.println:(I)V

7: return

LineNumberTable:

line 9: 0

line 10: 7

}

SourceFile: "ByteCodeTest.java"

类加载

双亲委派

双亲委派机制定义:当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。

Bootstrap ClassLoader(启动类加载器) :主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。

Extention ClassLoader(扩展类加载器):主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。

Application ClassLoader(应用程序类加载器) :主要负责加载当前应用的classpath下的所有类

User ClassLoader(用户自定义类加载器) : 用户自定义的类加载器,可加载指定路径的class文件

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

自定义类加载器

  1. 这个自定义的类加载器继承自ClassLoader

  1. 这个类加载器要重写ClassLoader类中的findClass()方法

Groovy + classloader

加载过程

类加载过程

加载(加载IO流文件,并存储类信息在方法区)->

验证(格式验证,语义分析,操作验证)->

准备(为类中的所有静态变量分配内存空间,并为其设置一个初始值)->

解析(将常量池中的符号引用转为直接引用)->

初始化(将一个类中所有被static关键字标识的代码统一执行一遍,如果执行的是静态变量,那么就会使用用户指定的值覆盖之前在准备阶段设置的初始值;如果执行的是static代码块,那么在初始化阶段,JVM就会执行static代码块中定义的所有操作)

static的代码(包括静态代码块)是在类加载的初始化阶段执行的。

final修饰的变量是在类加载的加载阶段执行的,比static靠前

https://blog.csdn.net/qq_29857681/article/details/85323603

小结:

 

内存分配与垃圾回收

内存分配

 

垃圾回收

怎么判定是垃圾?

可达性分析:“GC Roots”根对象集作为起始点集合,从这些节点开始,根据引用关系向下搜索,搜索过程路径称为“引用链”。如果,某对象到GC Root没有引用链相关联,那么,就是GC Root到对象不可达,则证明这个对象不可能再被使用。

 

 

固定作为GC Root的对象:

虚拟机栈中引用的对象,如:线程中被调用的方法堆栈中使用的参数、局部变量、临时变量。

方法区中类静态属性引用的对象。

方法区中常量引用的对象,比如:字符串常量池里的引用。

本地方法栈中JNI引用的对象。

Java虚拟机内部的引用,比如:基本数据类型对应的Class对象、常驻异常对象、系统类加载器,如:String、NullPointExcepition等。

所有被同步锁持有的对象(synchronized关键字)。

反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

怎么回收?

常见算法

  1. 标记-清除算法(Mark-Sweep)

    1. 首先标记出所有需要回收的对象,

    2. 然后回收所有需要回收的对象。

  1. 标记-整理(压缩)算法(Mark-Compact)

    1. 标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象向一端移动,然后直接清理掉这端边界以外的内存。

  1. 复制算法(Copying)

    1. 将可用内存划分为两块,每次只是用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉。

G1垃圾回收器(Garbage first

G1主要面向的是服务端的垃圾回收器。在G1之前,JVM的主要垃圾回收器采用的是物理分代的思想,将内存区域严格的划分成年轻代(young GC)和老年代(major GC),然后针对于年轻代和老年代使用不同的垃圾回收器进行GC操作,直到G1,G1采用的是对整个堆进行回收,并且G1使用的分区region思想将内存划分成了许多的分区。

主要特点

1. 按照region分块,每个块标记S,E,O,H

2. 按照性价比进行回收,分别统计每个块的回收空间和时间,按照性价比进行排序

3. 可以指定用户停顿时间,动态回收垃圾大小。默认200ms

https://blog.csdn.net/qq_29857681/article/details/125571888

JVM问题解决

OOM

 

减少Full GC

  • 首先考虑内存泄漏 dump线上内存 查看大对象/数量较多的对象 进行代码排查

  • 调大年轻代 让对象尽量在年轻代进行回收 少进入老年代

  • 同时要注意年轻代过大导致young GC时间过长,因此要在年轻代大小和GC时间之间平衡(不断调试)。

Full gc 优化:https://blog.csdn.net/cml_blog/article/details/81057966

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值