栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JVM学习----内存结构----程序计数器&虚拟机栈&本地方法栈&堆&方法区

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM学习----内存结构----程序计数器&虚拟机栈&本地方法栈&堆&方法区

JVM组成结构:
.class 字节码文件,被类加载器加载进内存。
类放在方法区,类创建的实例对象放在堆。对象调用方法时会涉及 虚拟机栈,程序计数器,本地方法栈。
解释器,即时编译器:方法执行时,每行代码由执行引擎中的解释器逐行执行,方法里的热点代码,就是频繁执行的代码,由即时编译器进行编译(可以理解为优化后执行)。
GC:GC模块,对堆里面不在引用的对象进行回收。
本地方法接口:一些不方便由java代码实现的功能,比如调用底层操作系统的功能,借助本地方法接口,来调用本地操作系统提供的一些方法。

内存结构 程序计数器(寄存器)

特点:
1. 线程私有。在多线程执行过程中,例如:线程1执行到 第九行代码,线程1的时间片结束,cpu执行线程2代码。那么线程1里面的程序计数器会将第十行代码的地址进行存储,每个线程都有一个程序计数器。只能线程内部进行访问。当cpu切换回线程1执行时,获取程序计数器的地址接着往下执行。
2. 程序计数器,唯一一个不会存在内存溢出的部分。

作用:加载下一条jvm指令的执行地址,物理上通过寄存器实现。

将java源代码编译为 二进制字节码,会将文件的首地址给解释器,同时将下一条 代码的地址加载进程序计数器。下一次解释器在进行代码执行时,就直接去程序计数器中获取地址。

虚拟机栈

先入后出
定义:
1. 每个线程运行时所需要的内存,称为虚拟机栈
2. 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
3. 每个线程只能有一个活动栈帧(栈顶部的那个栈帧),对应着当前正在执行的那个方法。

问题:
垃圾回收是否涉及栈内存? ---- 不需要,在出栈的时候就被回收了。

栈内存分配越大越好嘛? ---- 运行代码时可以进行指定,Linux默认1024KB,Windows依赖系统设置的虚拟内存。
栈越大,只是能增强你递归调用次数,不会增加你程序运行效率,在内存固定情况下,栈过大还会影响堆内存,导致线程并发数减少。可以使用 参数 -Xss 1m 进行指定。一般采用默认即可。

在idea中进行设置

栈内存溢出

什么情况下栈内存溢出?
1. 线帧过多,例如:递归没有正确的返回。
2. 栈帧过大

线程运行诊断

案例1:cpu占用过多
Linux下,top命令,相当于打开我们windows系统的进程管理器

ps命令可以查看 线程对cpu的占用情况
H : 展示进程里面所有线程
- eo :打印指定内容 例如下面,打印 pid,tid,%cpu
ps H -eo pid,tid,%cpu 查看所有进程的线程信息
ps H -eo pid,tid,%cpu | grep 32655 对信息进行过滤,只看指定进程

使用jdk提供的工具对指定进程进行查看,查看每个线程的详细信息。
jstack 进程id

我们需要将 十进制的 线程id转换为 十六进制的线程id 32665 ----> 7f99

具体定位到线程------并且定位到具体代码行数。

案例2:程序运行很长一段时间没有响应

排查线程死锁问题
1、查看java程序 的进程运行情况

2、 使用 jstack 命令 进程运行情况。
jstack 32752

本地方法栈

本地方法使用的内存就叫本地方法栈!
什么是本地方法,就是一些不方便由java实现的系统级别方法,有c或者c++实现的方法。java去进行调用,这些方法运行使用的内存就叫本地方法栈。

典型的,Object对象的clone方法,这种native方法,都是没有实现的,都是去调c或者c++实现的。

堆

通过new关键字创建的对象都会使用堆
特点:
1、它是线程共享的,堆中的对象都需要考虑线程安全问题
2、有垃圾回收机制

堆内存溢出

代码演示溢出

分析:
list集合一直被使用,不会被回收,然后字符串在list集合中被引用,所以这些字符串也不会被回收。最后导致内存溢出。

可以通过参数 -Xmx4g 进行堆内存指定大小。如果使用idea工具,可以直接在VM options中进行参数指定

堆内存诊断

1、jps工具
查看当前系统中有哪些java进程
2、jmap工具
查看堆内存占用情况 jmap -heap 进程id
3、jconsole工具
图形界面,多功能的检测工具,可以连续监测

示例代码:

jconsole演示:
在idea控制台输入jconsole指令

然后会出现一个图形化工具,选择要查看的进程。

监控界面展示

案例

现象:堆内存垃圾回收之后,内存占用仍然很高。

另一款图形化工具:jvisualvm

界面展示:连接对应java进程

进入堆监控,抓取堆的当前快照。可以对其进行分析

截取信息

方法区

定义:所有java虚拟机线程共享的区域。存储和类结构相关的信息,例如:成员变量,方法数据,成员方法和构造器方法,特殊方法。
方法区在虚拟机启动时创建,逻辑上是堆的一个组成部分。不同的jvm厂商,不一定遵从。这个规范并不强制方法区位置。
例如:jdk1.8之前,方法区的实现是永久带,永久带就是占用堆内存的一部分控件。
jdk1.8之后,方法区的实现是元空间,元空间使用的本地内存(操作系统内存)。

方法区内存不足时,也会抛 OutOfMemoryError 内存溢出异常

方法区内存溢出

代码演示:

1.8以前会导致永久代内存溢出

1.8之后会导致元空间内存溢出
使用系统内存,并且没有上限。一般很难内存溢出。除非自己进行参数设置
–XX:MaxMetaspaceSize=8m

实际场景:
spring,mybatis框架使用cglib动态代理时会产生大量class字节码文件。字节码动态生成技术。

运行时常量池

常量池,就是一张表,虚拟机指令根据这张表找到要执行得类名、方法名、参数类型、字面量等信息
我们可用通过 javap -v 文件名.class 指令对字节码进行反编译
二进制字节码文件,包含了,类基本信息,常量池,类方法定义,包含了虚拟机指令。

类的基本信息

常量池:

代码对应的虚拟机指令(后面注释时javap工具帮我们加上的),
解释器执行 0: getstatic #2 代码时,遇到#2就会去常量池中查询

运行时常量池,常量池时 *.class 文件中得,当该类被加载,它得常量池信息就会被放入运行时常量池,并把里面得符号地址变为真实地址(内存中的地址)。(把常量池放到运行时的位置就叫运行时常量池)

StringTable

先看几道面试题,如果全部答对没有疑惑。

false , true , true
1.8 false , 1.6 false
调换位置 x2.intern() ; String x1 = “cd”;
1.8 true 1.6 false



经过反编译我们可以发现,字符串拼接底层使用StringBuilder实现。
toString方法,底层使用的 new String() 创建的字符串对象。
s1 = “a” s2 = “b”
所以面试题中, s3 = “ab” s4 = s1 + s2(s1,s2是变量,运行时引用有可能修改) s3 == s4 ----> false s3在字符串常量池,s4在堆内存中。


s3 = “ab” s5 = “a” + “b” (这是javac在编译期的优化,结果在编译期间确定为ab)
两个都是指向常量池中同一个元素。所以 s3 == s5 -----> true

字符串常量池,不会在你声明定义变量的时候就将字符串加载进常量池。会在你是用的时候才会进行加载,一种懒加载机制。
例如:
String s1 = “a”; // 不会将字符 a 加载进常量池
System.out.print(s1) // 使用到了才会加载


inter 方法:

程序执行顺序:
1、将 字符串 a 放入常量池,并且在堆内存创建对象。同理b,创建 s 字符串对象,存放在堆内存。
2、调用 intern 方法,现在字符串常量池没有 字符串 “ab”,那么 s2 指向的是 字符串常量池中的 ab,并且 s 也是指向字符串常量池中的ab (当然 堆内存中的那个对象,如果其他地方也没有引用,那么就等待GC回收)
3、将 s2 == “ab” 做比较,现在常量池中已经有了 字符串 “ab”,所以这儿比较的ab,就是上一步放进去的
最后两个输出,都为 true

如果是1.6 true false



程序执行顺序:
1、将字符串 "ab"放入常量池
2、将字符串 “a” 放入常量池,并且在堆内存创建对象。同理 b,堆内存创建 s 对象。
3、将 s 对象放入常量池,但是现在 常量池中已经存在,所以不会放入。那么 s2 指向 字符串常量池中的 “ab”,但是 s 依旧是堆内存中的s
最后输出结果, true,false

如果是1.6 true,false

StringTable特性总结

1、常量池的字符串仅是符号,第一次使用到时才会变为对象(懒加载机制)
2、利用串池的机制,来避免重复创建字符串对象(底层使用的hashtable存储,并且不会扩容)
3、字符串变量拼接的原理是StringBuilder(1.8,变量拼接 s3 = s1 + s2)
4、字符串常量拼接的原理是编译期优化(字面量拼接 s3 = “a”+“b”)
5、可以使用intern 方法,主动将字符串常量池中还没有的字符串对象放入常量池
1)1.8 将这个字符串独享尝试放入串池,如果有则不会放入,如果没有则放入,会把串池中的对象返回。
2)1.6 将这个字符串独享尝试放入串池,如果有则不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1028580.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号