`

走进JVM,浅水也能捉鱼!

 
阅读更多

走进JVM,浅水也能捉鱼! - 沉默 - 断尘居
当jvm运行起来的时候,它会向系统申请一片内存区(不同的jvm实现可能不同,有些可以使用虚拟内存),将这块内存分出一部分存储许多东西,例如:程序 创建的对象,传递给方法的参数,返回值,局部变量等等,我们将这块内存称之为“运行时数据区”, 运行时数据区可以划分成方法区、堆、java栈、pc寄存器、本地方法栈。
看到上面这幅图,和这些解说你可能大概的明白jvm 体系是个啥样子,但是你或许还不了解运行时数据区里面方法区等用来干嘛的。

  • 方法区:当虚拟机装载一个class文件的时候,它会从这个class文件包含的二进制数据中解析类型信息。然这些类型信息放到方法区中。因为方 法区是被所有线程共享的,所以必须考虑数据的线程安全。假如两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只应该有一个线程去加 载,而另一个线程等待。
  • Pc寄存器:每个新线程产生都将得到自己的pc寄存器以及一个java栈帧。
  • 堆:存放程序运行时产生的所有对象。堆是一个线程共享的内存区,所以我们写多线程程序的时候需要考虑并发。
  • Java栈:java栈由许多栈帧组成的,如图,当一个线程调用java方法时,虚拟机压入一个新的栈帧到java栈中,当方法返回的时候,这个栈帧被从java栈弹出并被抛弃。


走进JVM,浅水也能捉鱼! - 沉默 - 断尘居

那么现在你应该可以想象到一些jvm是怎么工作的了,是不是应该接着讲具体工作原理了呢?。但是不急,先了解下类的装载机制。
了解类的装载机制之前先了解jvm里面的类装载器:Bootstrap Loader、ExtClassLoader 、AppClassLoader;
ExtClassLoader (负责装载jre下面的rt.jar, charsets.jar)和AppClassLoader(负责转载classpath下面的类包)是ClassLoader(抽象类)的子类;
Bootstrap Loader(负责装载jre核心类库)是根装载器是c/c++写的在java里面看不到它。
这三个类装载器存在父子关系, 根装载器是 ExtClassLoader父装载器,ExtClassLoader是AppClassLoader父装载器;
Jvm中类的装载也是安全机制沙箱模型的第一道门槛。 Java装载类使用“双亲委派模式”—即全盘负责委托机制。
好现在让我们了解装载大概流程;
当装载一个类的时候,若是由用户指定一个类装载器装载的话,那么那个类装载器会先委派给父类装载器,一直委派到根装载器,如果装载的是一个 java.lang.String,由于它是核心类库的而且已经被装载过了,那么就会直接返回一个class对象,那么如果是一个根装载器找不到的类呢? 接着就会交给子类(下一级父类)装载器,如果还是没有找到类文件,接着就会由之前用户指定的那个类装载器装载。(这里没有说明装载超类的过程,请勿疏 忽)。
如果是有人恶意的写了一个基础类java.lang.String,那么会影响虚拟机吗? 不会因为这个类最终会交由根装载器装载,而根装载器只会去jre核心类库加载,最终返回的class类型并不是 用户写的String,而且系统自带的String,也就是说用户写String永远不会被加载。
了解了类装载器是怎么工作了之后,我们也需要了解下class文件格式;
The ClassFile Structure
ClassFile{
u4 magic; //魔数
u2 minor_version; //class 次版本号
u2 major_version; //class 主版本号
u2 constant_pool_count; //常量池计数
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags; //修饰符
u2 this_class; /常量池索引
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attrributes_count];
}

我们需要了解的有很多,但是我们难以理解的就是 cp_info constant_pool 常量池;
一个常量池里面有很多表

CONSTANT_Utf8 UTF-8编码的Unicode字符串
CONSTANT_Integer int类型的字面值
CONSTANT_Float float类型的字面值
CONSTANT_Long long类型的字面值
CONSTANT_Double double类型的字面值
CONSTANT_Class 对一个类或接口的符号引用
CONSTANT_String String类型字面值的引用
CONSTANT_Fieldref 对一个字段的符号引用
CONSTANT_Methodref 对一个类中方法的符号引用
CONSTANT_InterfaceMethodref 对一个接口中方法的符号引用
CONSTANT_NameAndType 对一个字段或方法的部分符号引用

这些表结构我也不解释了
如果对class文件不够了解也没什么关系,知道个大概也行。那么我们了解了 jvm体系,类装载器工作流程,那么我们细看下 类装载器工作中 ,jvm运行时数据区的变化,方法区里面的结构等等。
在类装载的过程中, 每一个类装载器都会在方法区里面形成一张表,这张表记载着该装载器和对应的类的权限定名。没这么一张表就形成了jvm内部的命名空间。同时在方法区里面还该类的常量池等信息。
那么说到这些,其实这个过程还是很模糊,而且很多知识也落下了,那么我们现在看一个详细一点的装载过程。
当装载一个普通的类的时候,即调用类装载器的loadClass方法, 如果希望装载的类还没有被装载到命名空间,那么jvm会传递一个该类型的全限定名给类装载器,也就是常量池CONSTANT_Class_info(该表 存储着父类、类装载器等信息)入口的装载器,来试图装载被引用的类型,如果发起引用的类型是被jvm装载器定义的,那么由jvm类装载器装载,否则由用户 自定义装载器装载,那么一旦被引用的类型被装载了,jvm仔细检查它的二进制数据,如果类是是一个类,并且不是java.lang.Object。 jvm根据数据得到它的全限定名进行装载(递归的应用了)这个过程还需要递归超接口。

装载差不多讲完了,一个完整的过程 是: 装载—连接---初始化
那么连接和初始化就一带而过了, 重点放在垃圾回收。
连接的过程主要是验证(确认类型符合java语言的语义,并且它不会危及虚拟机的完整性)、准备(java 虚拟机为类变量分配内存,设计默认初始值)、解析(在类型的常量池中寻找类、接口、字段和方法的符合引用,把这些符号引用替换成直接引用的过程)。
初始化的时候,如果类存在直接超类,且超类还没有被初始化,就先初始化直接超类。初始化接口并不需要初始化它的父接口。
补充:
Jvm当运行某个方法的时候,先把这个方法压入java栈中,里面包含局部变量等信息,那么对象放入哪里呢? 压入栈的是对象的引用, 即变量, 所有的对象都存储在堆中。
为什么要把对象放入堆,把变量之类的数据放入栈呢? 说白了,对象太大了,存入栈中运算麻烦。(当然标准的回答不是这样的,我这里仅仅是说明实质)

了解了这么一个过程之后,我们必然要了解垃圾回收机制了。
基本回收算法
1. 引用计数:比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
2. 标记-清除:此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
3. 复制:此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每 次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现“碎片”问题。当然,此算法的缺点也是很明显的,就是 需要两倍内存空间。
4. 标记-整理:此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除 未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
5. 增量收集:实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。
6. 分代:基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。







====================================分割线====================================

问:堆和栈有什么区别?
答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的methodVar是在运行期存放到栈中的。 栈是跟随线程的,有线程就有栈,堆是跟随JVM的,有JVM就有堆内存。

问: 堆内存中到底存在着什么东西?
答:对象,包括对象变量以及对象方法。

问:类变量和实例变量有什么区别?
答:静态变量是类变量,非静态变量是实例变量,直白的说,有 static修饰的变量是静态变量,没有 static修饰的变量是实例变量。静态变量存在方法区中,实例变量存在堆内存中。

问:Java 的方法(函数 函数函数 函数)到底是传值还是传址?
答:都不是,是以传值的方式传递地址,具体的说原生数据类型传递的值,引用类型传递的地址。对于原始数据类型, JVM的处理方法是从 Method Area或Heap中拷贝到 Stack,然后运行 frame中的方法,运行完毕后再把变量指拷贝回去。


问:为什么会产生OutOfMemory?
答:一句话:Heap 内存中没有足够的可用内存了。这句话要好好理解,不是说 Heap没有内存了,是说新申请内存的对象大于 Heap空闲内存,比如现在 Heap还空闲 1M,但是新申请的内存需要 1.1M,于是就会报 OutOfMemory了,可能以后的对象申请的内存都只要0.9M,于是就只出现一次OutOfMemory,GC也正常了,看起来像偶发事件,就是这么回事。 但如果此时 GC没有回收就会产生挂起情况,系统不响应了。

问 :我产生的对象不多呀 ,为什么还会产生OutOfMemory?
答:你继承层次忒多了,Heap中 产生的对象是先产生 父类,然后才产生子类,明白不?


问:OutOfMemory 错误分几种?
答:分两种,分别是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGen space”,两种都是内存溢出,heap size是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。 “PermGen space”是因为永久存储区满了,这个也很常见,一般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。


问:为什么会产生StackOverflowError?
答:因为一个线程把 Stack 内存全部耗尽了,一般是递归函数造成的。

问:一个机器上可以看多个JVM吗?JVM 之间可以互访吗?
答:可以多个JVM,只要机器承受得了。JVM之间是不可以互访,你不能在A-JVM中访问B-JVM的Heap 内存,这是不可能的。在以前老版本的JVM中,会出现A-JVM Crack后影响到B-JVM,现在版本非常少见。

问:为什么Java要采用垃圾回收机制,而不采用 C/C++的显式内存管理?
答:为了简单,内存管理不是每个程序员都能折腾好的。

问:JVM 中到底哪些区域是共享的?哪些是私有的?
答:Heap和 Method Area是共享的,其他都是私有的,

问:什么是JIT?
答:JIT 是指Just In Time,有的文档把JIT 作为JVM的一个部件来介绍,有的是作为执行引 擎的一部分来介绍,这都能理解。Java刚诞生的时候是一个解释性语言,别嘘,即使编译成了字 节码(byte code)也是针对JVM的,它需要再次翻译成原生代码(native code)才能被机器执行,于 是效率的担忧就提出来了。Sun为了解决该问题提出了一套新的机制,好,你想编译成原生代码, 没问题,我在JVM上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生 码就成了,于是JIT 就诞生了,就这么回事。

问:JVM 还有哪些部分是你没有提到的?
答:JVM是一个异常复杂的东西,写一本砖头书都不为过,还有几个要说明的:
常量池(constant pool):按照顺序存放程序中的常量,并且进行索引编号的区域。比如 int i =100,这个 100 就放在常量池中。
安全管理器(Security Manager):提供Java运行期的安全控制,防止恶意攻击,比如指定读取文件,写入文件权限,网络访问,创建进程等等,Class Loader在 Security Manager认证通过后才能加载 class文件的。
方法索引表(Methods table),记录的是每个 method 的地址信息,Stack和 Heap 中的地址指针其实是指向 Methods table地址。

问:为什么不建议在程序中显式的生命System.gc()?
答:因为显式声明是做堆内存全扫描,也就是 Full GC,是需要停止所有的活动的(Stop The World Collection),你的应用能承受这个吗?而其显示调用System.gc()只是给虚拟机一个建议,不一定会执行,因为System.gc()在一个优先级很低的线程中执行。

问:JVM 有哪些调整参数?
答:非常多,堆内存、栈内存的大小都可以定义,甚至是堆内存的三个部分、新生代的各个比例都能调整。


分享到:
评论

相关推荐

    全套JVM面试题!面试必备!

    【JVM面试题详解】 Java虚拟机(JVM)是Java平台的核心组成部分,它负责运行Java应用程序并提供跨平台的兼容性。对于Java开发者来说,深入理解JVM的工作原理是面试中不可或缺的一部分。以下是一些关于JVM的常见面试...

    java8源码-jvm-on-go:基于golang的简单jvm!只为学习!

    Jvm On Go 使用golang基于 构建的简易JVM,仅供学习使用。 Roadmap 类加载 字节码解析 类解析及初始化 字节码解释器 单线程 部分Native方法(使用golang实现) invokedynamic Lambda Getting Started 1. 环境准备 本地...

    绩点计算器(高校通用版)(需要电脑有JVM环境)

    有JVM才能运行!!!

    推荐一些JVM原理,JVM调优,JVM内存模型,JAVA并发 电子书1

    标题中提到了JVM原理、JVM调优、JVM内存模型和JAVA并发,这些都是Java虚拟机(JVM)相关的核心概念。JVM是运行Java字节码的虚拟计算机,为Java提供了一个跨平台的环境,确保Java程序可以在不同的操作系统上运行而...

    JVM 深入学习教程深入分析JVM教程!jvm 内存原型,优化等等

    Java虚拟机(JVM)是Java程序运行的基础,它提供了执行字节码的环境,使得Java具有跨平台的能力。深入学习JVM对于优化Java应用程序性能、理解和解决内存问题至关重要。本教程将涵盖JVM内存模型、内存分配以及优化...

    jdk,jvm源码

    Java虚拟机(JVM)是Java程序运行的核心,它负责解释和执行字节码,为Java应用程序提供了一个跨平台的运行环境。JDK(Java Development Kit)包含了开发和运行Java程序所需的所有工具,包括JVM。当我们谈论"jdk,jvm...

    hawtio:Hawtio Web控制台可帮助您管理JVM内容并保持冷静!

    您可以使用自己的插件动态扩展Hawtio或自动在JVM中发现插件。 唯一的服务器端依赖性(静态HTML / CSS / JS / images除外)是出色的,该占用空间小(约300KB),可以作为,也可以作为servlet嵌入在hawtio-default....

    jvm 启动过程 JVM 原理

    在JVM的工作过程中,运行时数据区(也称为Java堆内存)是另一个重要的概念。它分为以下区域: - **堆**:存储所有实例对象和数组。Java垃圾收集器负责管理堆内存,进行对象创建和销毁。 - **栈**:每个线程都有...

    接口偶尔超时,竟又是JVM停顿的锅!.doc

    《接口偶尔超时,JVM停顿的影响及深入解析》 在我们的日常运维工作中,有时会遇到一个令人头疼的问题——接口出现偶尔的超时现象。这种情况可能会对系统的稳定性造成严重影响,用户体验也会大打折扣。本文将围绕一...

    jvm 详细介绍,了解jvm各个组成部分和功能

    ### JVM 详细介绍:掌握 JVM 的各个组成部分与功能 #### 一、Java 源文件编译及执行 Java 应用程序的核心在于源文件的编译与执行。不同于 C/C++ 这类需要针对不同平台进行编译的语言,Java 采用了一种更为灵活的...

    JVM图解-JVM指令-JVM原型图.rar

    在这个压缩包中,"JVM图解.png"可能是对JVM内部结构的可视化表示,"JVM图解"可能是一个详细的文档,解释了JVM的工作原理,而"JVM指令手册 中文版"则提供了JVM可执行的所有指令的详细信息。下面,我们将深入探讨JVM的...

    SAP JVM 8.1 64 bits

    例如,Java 8引入了Lambda表达式、Stream API等新特性,SAP JVM 8.1应该也支持这些特性。同时,版本更新可能包括安全修复、性能改进和兼容性增强。 4. **包含的文件**: - `COPYRIGHT`:包含了软件的版权信息,...

    jvm-mon基于控制台的JVM监视

    6. **系统资源监控**:除了JVM内部,`jvm-mon`还能提供操作系统级别的信息,如CPU、内存和磁盘使用情况,以全面了解系统健康状况。 ### 使用jvm-mon 要使用`jvm-mon`,首先需要从源代码仓库(如GitHub)克隆或下载...

    JVM中文指令手册.pdf

    JVM指令手册详细记录了JVM的所有操作码(opcode),也就是字节码指令。这些指令是给JVM解释器或者即时编译器(JIT)使用的低级指令集。在JVM上运行的Java程序会被编译成一系列指令,然后由JVM执行。 从给定文件的...

    jvm视频及笔记

    Java虚拟机(JVM)是Java程序运行的核心组件,它负责解释和执行字节码,为开发者提供了跨平台的运行环境。"jvm视频及笔记"这个资源显然是一份全面学习JVM的材料,结合了视频教程和书面笔记,帮助学习者深入理解JVM的...

    JVM必知必会

    ### JVM必知必会知识点梳理 #### 1. JVM的定义与层次 Java虚拟机(JVM)具有多重含义: - **一套规范**:即Java虚拟机规范,定义了Java虚拟机应该具有的行为。 - **一种实现**:例如HotSpot、J9、JRockit,它们都是...

    狂神说JVM探究.rar

    【狂神说JVM探究】是一份集合了多种格式的学习资料,主要涵盖了Java虚拟机(JVM)的基础知识。这份资料出自B站上的【狂神说Java】系列教程,为快速入门JVM提供了详实的笔记。以下是根据这些资源可能包含的一些关键...

Global site tag (gtag.js) - Google Analytics