JVM各种部分的工作原理

本文详细介绍了JVM的工作原理,包括什么是JVM、JVM内存结构(如程序计数器、虚拟机栈、本地方法栈、堆和方法区)以及垃圾回收机制。讨论了不同内存区域的作用、特点、溢出情况及调优方法。同时,文章涵盖了类加载过程、字节码文件、类加载器和JMM(Java内存模型),强调了原子性、可见性和有序性,并探讨了CAS和原子类的使用。
摘要由CSDN通过智能技术生成

什么是JVM

jvm是java二进制文件(.class)的运行环境
好处
1.一次运行,处处运行(可移植性)
2.自动内存管理,垃圾回收功能
3.数据下标越界检查,会抛出异常,不会覆盖数组的其他元素
4.采用虚方法表进行多态,多态是指同一操作作用于不同的对象

比较
在这里插入图片描述

JVM的内部结构和执行过程
在这里插入图片描述

1.java文件首先被编译成二进程class文件。
2.class文件被类加载器classloader加载到jvm中
3.类都被放在jvm的方法区method Area中 ,产生都对象都被放在堆中heap
4.对象在调用方法时就会用到虚拟机栈jvm stacks ,程序计数器pc register和本地方法栈native method stacks
5.方法执行时,由执行引擎中的解析器interpreter逐行执行,把字节码文件解释成机器码,交给CPU执行;被频繁使用的代码会被即时编译器 JIT Conmpiler优化后执行
6.垃圾回收器会对堆里面不再被引用的对象进行回收,本地方法接口用于调用操作系统的方法与JVM联系。

学习路线 -------1.jvm内存结构--------2.jvm内存回收--------3.java文件变成class文件的优化-----4.类加载器----5.对热点代码进行编译优化的即使编译器

JVM内存结构

1.程序计数器 PC Register ——由CPU中的寄存器实现
程序计数器的作用
用来记录下一条jvm指令的地址,交给解释器编译成机器码。

阅读以下代码
在这里插入图片描述

JAVA源代码首先被编译成二进制字节码和jvm指令,而程序计数器会记住下一条jvm指令执行的地址,然后解释器从程序计数器中拿到执行的指令,并把二进制字节码释成机器码,CPU再对机器码进行执行和处理。

程序计数器的特点
(1).程序计数器是线程私有的,每一个线程都有自己单独的程序计数器。

线程私有的好处

CPU执行多个线程时采用时间片轮转,当线程1执行到某一个JVM指令时间消耗完毕时,程序计数器会记录下一个jvm指令的地址,此时会暂停线程1执行线程2.当线程2时间到达又执行线程1时,就会继续执行在线程1中的程序计数器中记录的jvm指令。

(2).程序计数器不会存在内存溢出

2.虚拟机栈 JVM Stacks——线程私有的内存空间
(1).什么是虚拟机栈
虚拟机栈是线程运行时需要的内存空间。一个线程对应一个栈,是线程私有的,每一个栈都是由多个栈帧元素组成,但只能由一个活动栈帧;
什么是栈帧
栈帧是每个方法运行时需要的内存空间,一个栈帧对应一次方法的调用,存储了操作数栈,局部变量表和方法返回值,动态连接。

当对象需要具体调用方法时,首先会创建一个栈帧,为方法开辟空间,然后存入栈帧中;然后把栈帧压入栈中,最后把栈放入到对应的线程之中交给cpu执行。

(2).常见问题

a.垃圾回收不会涉及栈内存,因为在方法调用结束后,虚拟机栈就会释放掉栈帧,方法的内存空间也就被释放了。

b.栈内存分配不是越大越好,当栈内存分配大时,会导致线程数变少,降低cpu线程并发的数目。

c.栈帧内的局部变量是线程安全的,但是方法参数和返回值不是线程安全的。因为每一个线程调用此方法时,都会创建新的栈帧,局部变量不会收到影响,初始值和结果一致,但是方法参数和返回值作为开头和结果可能会被其他线程影响。
如下:
在这里插入图片描述

方法m1中的stringbuilder是线程安全的,因为是局部变量;方法m2和方法m3中的stringbuilder不是线程安全的,因为可以被外部引用修改。

(3).虚拟机栈内存溢出-——java.long.StackOverflowError

-Xss可以设置虚拟机栈内存大小

a.栈帧过多,导致栈内存溢出;

如在方法递归调用中,没有设置正确的结束条件,
就会导致栈中的栈帧越来越多,且无法释放,此时栈内存就会溢出。

b.栈帧过大,导致栈内存溢出;

如一个栈帧的内存超过了栈的内存,导致内存溢出

(4).线程运行诊断

a.某一个线程导致cpu占用过多

1.linux中定位出占用过多的线程
	top:
		定位出进程那个进程对cpu占用过高
	ps H -eo pid,tid,%cpu  |grep  进程id(如 1234) :
		定位出进程中那个线程对cpu占用过高
	jstack 进程id:
		JDK提供的查看每一个线程的执行情况,找出对应的cpu占用过高的线程和代码查看
2.window中直接打开任务管理器即可。

c.程序运行很长时间都没有结果

1.多个线程发生了死锁
	jstack 进程id:
		会提示出相应死锁的线程和具体的某一行代码。

3.本地方法栈——线程私有的内存空间
(1).什么是本地方法栈
本地方法栈是JVM在调用本地方法时本地方法所需要的内存空间,本地方法栈是线程私有的。

本地方法栈的作用

给本地方法提供内存,让JVM可以调用不是java代码编写的底层方法,实现一些底层的功能。

4.堆——线程共享的内存空间
(1).什么是堆
堆用于存储new关键字产生的对象

(2).堆的特点

a.堆是线程共享的,堆中的对象都需要考虑线程安全的问题

b.堆有垃圾回收机制

(3).堆内存溢出——java.lang.OutofMemoryError
-Xmx 设置堆空间的大小
当不断的产生新的对象,并且这些对象都不断的在使用,对象无法进行垃圾回收,机会导致堆内存溢出

堆内存溢出诊断工具

1.jps工具
	jps
	查看系统中有哪些进程
2.jmap工具
	jmap -heap 进程ID
	查看某一个进程某一个时刻的堆内存占用情况
3.jconsole工具
	图形界面的检测工具,连续检测堆内存,cpu占用率,进程。

常见问题和解决办法

多次垃圾回收以后,内存占用率仍然很高
1.首先利用jps,jmap或者jconsole常看内存占用情况
2.使用jvisualvm的 堆dump 功能抓取堆的当前时刻的使用情况。
在这里插入图片描述在这里插入图片描述

3.点击查找,选择查看内存最大的对象个数(默认20)
在这里插入图片描述
4.然后点击进入到需要查看的对象当中,就可以查看此对象占用内存的具体情况
在这里插入图片描述
在这里插入图片描述

5.方法区
二进制字节码(class文件):类基本信息,常量池,类定义方法,虚拟机指令。
方法区在虚拟机被启动的时候创建,用于存放二进制字节码(class文件)的相关信息。1.6之前由jvm管理,占用堆内存,常量池存放stringtable,1.8之后不由jvm管理,不占用堆内存,占用操作系统内存,常量池不存放stringtable。
在这里插入图片描述

(1).方法区内存溢出

-XX 设置方法去内存大小

加载的类太多,导致方法区空间存放不下。

1.6以前会导致 永久代 内存溢出——java.lang.OutOfMemoryError:PermGen space
1.8以后会导致 元空间 内存溢出——java.lang.OutOfMemoryError:Metaspace

(2).方法区特点

a.方法区是线程共享的

b.方法区存在垃圾自动回收机制

(3).运行时常量池

常量池就是一张表,jvm指令根据常量表找到执行的类名,方法名,参数类型,变量等信息。
在这里插入图片描述

运行时常量池是用来存放被加载的class文件的常量池,并把以前常量池的符号地址变成真实地址。

(4).StringTable(字符串常量池)
用于存放字符串,是hashtable结构,采用数据+链表的形式,不能扩容。

使用 javap 进行反编译,分析如下代码1
在这里插入图片描述

当demo1_22这个类被加载到方法区的时候,会把class的常量池信息加载到运行时常量池,此时string的对象还仅仅时符号,当加载到某一个string的对象才会变成真正对象,并把相应的数据存放到字符串常量池stringtable中。

加入如下代码2后分析:

String s4 = s1+s2 ;

在这里插入图片描述

首先会创建一个StringBuilder的对象,调用StringBuilder的 init()方法,初始化StringBuilder,然后调用加载s1,调用StringBuilder的append方法,把s1放入到StringBuilder中,s2同s1一样,最后调用toString方法,此方法会new一个新的字符串,把结果存入到s4的变量中,因为是new出来的,所以把s4变量放入堆里面。

s3在字符串常量池中,s4在堆中,所以不是一个对象。

加入如下代码3后分析:

String s5 = "a"+"b";

在这里插入图片描述

首先javac会在编译期间对s5进行优化,直接得到结果“ab”,所以,从第29条指令和30条指令可以发现,s5直接去字符串常量池中找到了 字符串ab ,然后把值给s5。

s5和s3都是同一个地址和对象,因为他们都指向了字符串常量池中的ab。

添加以下代码

s4.intern();
JDK1.8
作用:如果字符串常量池中不存在s4的字符串对象,就把字符串对象s4放入串池中,
	此时,s4变成了串池中的对象。
	如果存在就不进行放入,并且会把串池中存在的对象返回。

s4.intern();
JDK1.6
作用:如果字符串常量池中不存在s4的字符串对象,就把字符串对象s4复制一份放入串池中,
	此时,s4还是堆中的对象,复制的才是串池中的对象。
	如果存在就不进行放入,并且会把串池中存在的对象返回。

JDK1.8

在这里插入图片描述

可以看到,s6对象不等于s4对象而等于s3对象,说明了s6对象是串池中s3对象返回的,s4对象是堆中的。
在s4之前,串池中已经有了“ab”对象,所以调用 intern() 方法,s4也不会被放入到串池中,所以s4不会等于s6和s3。

总结:

常量池中得字符串仅仅是符号,第一次使用时才会变成对象
利用字符串常量池,可以避免重复创建字符串对象,节约空间和性能
字符串 变量 拼接是利用了Stringbuilder,常量 拼接会在编译器优化
可以使用 intern 方法 ,主动将堆中的字符串,放入到字符串常量池中,前提是串池中没有这个对象。

StringTable的位置

1.6之前StringTable在永久代中,存放在常量池里
1.7开始StringTable放在堆中。

StringTable的垃圾回收机制

当StringTable的内存紧张的时候,就会促发垃圾回收机制,回收最长时间没有使用的字符串常量。

StringTable性能调优
String

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值