freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Z3专栏 | JVM浅析
2021-12-28 12:10:54
所属地 辽宁省

十二、JVM浅析

本来打算学习类加载的,但是类加载时发生了什么,加载后的class是什么形式存在?

所以打算先简单了解下jvm。

java语言

java是解释型语言还是编译型语言?

虽说java代码运行前需要编译,但编译后的结果是.class文件,里面是字节码,字节码需要在jvm中运行。而jvm依附于操作系统运行。编译型语言编译后得到的是机器码,将机器码写入内存,当前操作系统就可以直接调这些机器码运行。

所以java是解释型语言,由于字节码在虚拟机运行,所以会导致效率低。但是java在经过这么久的更新迭代,已经有了很多优化方案了。例如jit(运行时编译),aot(运行前编译)。

jvm意思是java的虚拟机,这个虚拟机的实现有很多种,最流行的就是HotSpot虚拟机,用了JIT及自适应优化技术。sun公司在jdk8后,默认的jvm就是HotSpot vm。

在运行java代码时可以指定以下参数,指定字节码的运行方式:

-XX:RewriteFrequentPairs 用于开启动态编译。
-Xint:禁用JIT编译,即禁用两个编译器,纯解释执行。
-Xcomp:纯编译执行,如果方法无法编译,则回退到解释执行模式解释无法编译的代码。

还想到一种优化方案:

如果将jvm做成物理机,那java就算编译型语言了(但是成本特别高,相同成本的虚拟机效率要远远高于物理机)。

JVM介绍

JVM组成

首先,JVM是指java虚拟机,而java虚拟机有很多种,最流行的就是HotSpot,JVM和HotSpot的区别就像 水果 与 苹果、香蕉、菠萝、哈密瓜 的区别

JVM有很多种,但它们都遵从JVM规范,以下学习的就是JVM规范的虚拟机结构,适用于所以JVM

JVM 整体组成可分为以下四个部分:

类加载器(ClassLoader)# 用于加载字节码

运行时数据区(Runtime Data Area)# 储存运行时产生的数据

执行引擎(Execution Engine)# 解析字节码,调用当前系统api去实现字节码的功能

本地库接口(Native Interface)# 调用其它语言的方法

而运行时数据区又可以分为以下几个部分:

程序计数器(Program Counter Register)# 标志着当前字节码执行到第几行(像汇编中的ip寄存器)

Java虚拟机栈(Java Virtual Machine Stacks)# 记录java方法调用顺序的栈

本地方法栈(Native Method Stack)# 记录native方法调用顺序的栈(JVM规范没对这里做要求,所以有的虚拟机直接将这部分合并到了Java虚拟机栈)

Java堆(Java Heap)# java虚拟机占用内存最大的一块,用来储存对象实例

方法区(Methed Area)# 用来储存被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据(常量池也在这里)

它们关系如图

图中提到了,运行时数据区在所有线程间共享、运行时数据区线程私有。
运行时数据区在所有线程间共享意思就是,无论创建多少线程,这些线程都储存到这个区。
运行时数据区线程私有就是,每创建一个线程,对应的这部分数据区也要创建,每一个线程都要有一个自己的数据区。
对应上图,绿色是线程们共用的,黄色的每个人都有一份自己的。

堆和栈是无论什么语言都会用到的数据结构。java中的堆用于储存对象实例,栈用于储存局部变量和方法调用(就是前几篇,用idea调试的时候,看见的方法调用栈)。
当空间不足时会报错:

栈空间不足:java.lang.StackOverFlowError。
堆空间不足:java.lang.OutOfMemoryError。

Java堆分为新生代和老年代,新生代可以分为Eden空间、From Survivor空间、To Survivor空间(这些空间了解下就行,分代用的)
如图

图中的GC 过程,是指垃圾回收过程,回收那些没人要的对象。GC过程有三种,Minor GC、Major GC 和 Full GC,分别对应年轻、老年、全部区域进行垃圾回收。这个过程还会对对象分代,例如xx对象存活时间久,那就扔进老年代区域。

为什么要分代?

上面图中提到了,GC过程耗时较长,要先扫描,再确定哪些回收,如果不分代,则每次GC都要扫描整个堆内存,所以很耗时。例如有个对象存在于堆内存中很久了,每次扫描都扫描到它,但它每次扫描都存活,那GC是不是应该“聪明点”,下次尽量不扫描它。

那怎么做?

划分一块内存区域,把这些钉子户,都放进这块内存,GC扫描内存时,不扫描这里,如果其它内存实在不能释放了,再扫描这块内存。

其实这就是分代,通过分代划分内存区域,不同代使用不同的回收机制,例如面上的回收机制就可以粗略的总结为年轻代进行GC频率高,老年代GC频率低,这样既保证了即时GC,又在GC过程中不需要扫描整个堆内存,提高了GC效率。

JVM如何加载类

java的类加载,有个大名鼎鼎的双亲委派机制。加载类需要用到类加载器,java语言支持下面4种类加载器:

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文件

双亲委派机制就是下面这张图

这些ClassLoader加载class时,都会将加载类的请求向上委托,如果父加载器都不能加载,再自己加载,因为每个加载器各司其职,只会加载自己负责的区域的类。
在加载器加载类时,都会检查这个类是否加载过,避免重复加载,检查范围是当前加载器加载过的类。

所以在java中,类的唯一性不仅由类名,包名决定,还有它的类加载器,即,判断两个类是否相等,先判断包名,再判断类名,再判断类加载器。
所以可能会出现两个类名包名相同的类,但他们类加载器不同,则这两个类类型也不同,不能互相做转换,赋值等操作。
例:同一个java类由不同的classloader加载问题

类被加载后,会存到方法区,方法区内的数据,在进程结束前都不会被删掉,所以方法区也被叫做永久区。

加载类不等于初始化类,加载类只是将字节码存储到方法区,,只有触发类初始化条件(我将这些条件总结为当使用到类时)时才会初始化。初始化时会从上到下执行静态代码。

代码细节在下一篇类加载的利用中详细分析。

怎么看见JVM

JVM是java虚拟机,很像一个简陋的操作系统,它有自己的指令集(class文件就包含jvm指令)、自己的命令、环境变量等等。
平时运行java文件,运行jar包,jvm都是在背后偷偷运行的,让我们看起来,java和c语言一样,都是编译后直接运行。

JVM在运行java程序时创建,每个java程序创建,都会创建相应的JVM。

在配置好java环境变量后,打开cmd,执行jvisualvm就可以看见当前所有的java进程,查看进程运行状态,还可以安装btrace插件,在进程中植入代码。

运行jConsole就可以看到下面如图,不仅可以查看本地进程信息,还可以看远程java进程信息,和jVisualvm类似。

通过这些工具,可以实时监控java程序运行状态,甚至修改java进程。JVM还有自己的命令,如jps,jstat等等,可以通过这些命令,可以来查看,修改jvm信息,达到JVM调优的目的。

# java漏洞 # java # java反序列化 # Java代码审计 # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录