一.jvm体系结构

1.jvm整体结构

jvm总体上是由类装载子系统(ClassLoader)、运行时数据区、执行引擎三个部分组成。

(jvm本质上就是一个java进程)

2.jvm生命周期

(1)jvm启动:通过一个引导类加载器创建一个初始类来完成,这个类由虚拟机具体实现指定

(2)jvm运行:执行java程序

(3)jvm退出:①程序执行结束或遇到异常②某线程调用System或Runtime类的exit方法或Runtime的halt方法

3.jvm发展历程

(1)sun classic VM:世界上第一款商用虚拟机(jdk1.4时被完全淘汰)只有解释器,hotspot内置此虚拟机

(2)exact VM:JDK1.2引入,可以知道内存中某个位置的数据具体是什么类型(eg:不能判断某个数据是数值还是地址,要通过handler句柄去找,很麻烦)

(3)hotspot VM:jdk1.3称为默认虚拟机。具备热点代码探测技术(引入方法区的概念)

(4)Jrockit:专注于服务器端应用。不关注启动速度,因此不包含解析器实现,所有代码都靠即时编译器编译后执行

(5)J9:ibm公司,广泛应用与ibm的各种java产品

二.类加载子系统

1.作用

加载class文件,加载的类信息存放于方法区(除类信息外方法区还有运行时常量池,字符串字面量和数字常量)

2.加载过程

1.加载:通过类的全限定名获取类的二进制字节流(从磁盘或网路加载类到内存),在方法区生成一个代表这个类的class对象

2.链接:

(1)验证:确保class文件包含的信息符合当前虚拟机的需求

(2)准备:为类变量分配内存(方法区),并设置默认初始值,即0(注意:不包含final修饰的static,因为在编译阶段就会初始化)

(3)解析:将常量池内符号引用转化为直接引用(加载类中用到的其它类如System)

3.初始化

类初始化阶段是类加载过程的最后一步,到了这个阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。

静态初始化——父类初始化——子类初始化

父类静态成员和static块-子类静态成员和static块

父类普通成员和非static块-父类构造函数

子类普通成员和非static块-子类构造函数

3.类加载器分类

(1)启动类加载器:使用c/c++实现,加载java的核心库(jre的lib下rt.jat)

(2)扩展类加载器:java编写,父加载器为启动类加载器(从jre/lib/ext下加载类库)

(3)应用类加载器:程序中默认的类加载器(加载classpath下的类库)

4.双亲委派机制

1.机制

(1)一个类加载器收到类加载请求,不会自己加载,而是交给父类的加载器去加载

(2)父类加载器还存在父加载器,进一步向上委托,直到达到顶层的启动类加载器

(3)父类加载器可以完成类加载,就成功返回。如果不能子类加载器才尝试加载

2.好处

(1)避免类重复加载,父classloader加载后没必要子classloader再加载

(2)安全保证,java核心api定义的类型不会被随意替换

三.运行时数据区

1.程序计数器

1.程序计数器作用:指向下一条指令的地址 线程私有(唯一一个再jvm中没有内存溢出的区域)

2.为什么需要?:cpu需要不停的切换各个线程,切换回来后需要知道从哪开始继续执行

2.虚拟机栈

1.概述

每个线程创建时都会创建一个虚拟机栈,内部保存着一个个栈帧,对应着方法的执行

2.设置栈内存大小

-Xss:设置线程最大的栈空间

3.栈帧

(1)局部变量表:主要用于存储方法参数和方法体内的局部变量(基本单位是slot(变量槽)32位以内占一个槽,64位占两个槽)

变量的分类:

成员变量:又分为类变量和实例变量(根据是否static修饰)

局部变量:方法内的变量

(2)操作数栈:在方法执行过程中,根据字节码指令往栈中写入或提取数据。即入栈/出栈

(3)动态链接:将符号引用转化为直接引用

每个栈帧内部包含一个指向运行时常量池中该栈帧所属方法的引用(可以理解为栈中保存的方法的地址,真正的方法结构是在方法区的运行时常量池中)

(4)方法返回地址:调用程序计数器的值作为返回地址。即调用吓一条指令的地址

3.本地方法栈

作用:用于管理本地方法(native)的调用

4.堆

1.概述

所有的对象和数组都应当在运行时分配在堆上 。

2.堆的内存细分

java8前,新生区(Eden和Survivor)+老年区+永久区

java8后,新生区(Eden和Survivor)+老年区+元空间

3.堆空间大小设置

(1)

-Xms:表示堆区的起始内存

-Xmx:表示堆区的最大内存(超出OOM)

(通常两个参数值相等,避免了gc后频繁的调整堆内存大小)

(2)默认初始大小:电脑内存/64

,最大内存大小 电脑内存/4

(3)查看设置的方式

方式一:

jps (查看java进程)

jstat -gc 进程id (显示gc相关的堆信息)

方式二:

-XX:+PrintGCDetails

4.年轻代与老年代

(1)参数配置

①默认-XX:NewRatio 新生代占1,老年代占2

②默认-XX:SurvivorRatio=8 Eden和两个survivor-1:8:8

5. 对象分配一般过程

1.new的对象先放到Eden,此区有大小限制

2.当Eden满时,会进行young gc(stop the world),将Eden死亡的对象销毁,加载新的对象进eden

3.将Eden剩余的对象放到survivor0

4.再次gc时,会将eden和survivor0存活的对象放到survivor1

5.当对象的年龄超过一定次数(默认15)进入老年区

(-XX:MaxTenuringThreshold=15)

6.对象分配的特殊情况

1.对象过大,Eden区放不下,直接放到老年代 (老年代也放不下进行old gc)

2.从eden放survivor的对象放不下,直接放老年代

7.yong gc ,old gc, full gc?

1.yong gc:只是新生代的垃圾收集(eden,s0,s1)(stw)

2.old gc:针对老年代的收集(stw)

(stw原因:避免垃圾回收的时候用户线程再产生垃圾)

3.full gc:针对整个堆和方法区的垃圾收集

full gc出发机制:①调用System.gc() ②老年代空间不足 ③方法区空间不足

5.方法区

1.概述

和堆一样,线程共享。存储 类信息,常量,静态变量,编译后的代码。实现方式是永久代(1.8之前)和元空间(1.8之后)(非堆)

2.永久代和元空间的区别?

1.永久代使用jvm内存,元空间使用本地内存

2.运行时常量池从永久代放入堆中

3.参数设置

1.8之前:

-XX:permsize 设置永久代初始空间

-xx:maxpermsize 永久代最大空间

1.8之后:

-xx:metaspacesize 默认元空间大小

-xx:maxmetaspacesize 最大元空间大小

4.常量池和运行时常量池

字节码文件,内部包含了常量池

方法区,内部包含了运行时常量池

1.常量池

一个java源文件中的类,接口编译产生字节码文件。代码中可能用到system,print等结构。如果把他们所有的信息都存在这个class文件,那文件就会非常大。所以把他们标识存到常量池。所以它可以看作一个表,虚拟机指令根据这张表找到曜执行的类,方法等。

2.运行时常量池

类加载后会将常量池中的内容加载到方法区的运行时常量池中

5.jdk6 7 8 方法区的演进?

jdk1.6:有永久代,静态变量存放在永久代

jdk1.7:有永久代,字符串常量池,静态变量保存在堆

jdk1.8:永久代变元空间。类信息,常量保存在元空间。字符串常量池,静态变量仍在堆

6.jdk1.7后字符串常量池为什么放到堆中?

永久代回收效率很低,在full gc的时候才会触发。而full gc是老年代空间不足,永久代不足时才会触发。这就导致字符串常量池回收效率不高。开发过程我们会创建大量的字符串。所以放到堆里能及时回收

6.对象的实例化

1.对象创建的方式

1.new

2.Class的newInstance()

3.Constructor的newInstance方法

4.clone() 不调用任何构造器,当前类实现cloneable接口,实现clone方法

5.使用反序列化

2.创建对象的步骤

1.判断对象对应的类是否加载,连接,初始化

2.为对象分配内存

(1)如果内存规整,虚拟机采用指针碰撞来分配内存。所有用过的内存放在一边,空闲的内存放在另一边。中间放着一个指针作为分界点指示器,分配内存就仅仅把指针空闲那一边挪动一段与对象大小相等的距离

(2)如果内存不规整,虚拟机采用空闲列表法来分配内存。列表上记录哪些块是可用的,在分配的时候从列表找到一块足够大的空间分配给列表实例

3.处理并发安全问题。采用cas失败重试,区域加锁保证更新的原子性

4.初始化属性值

5.设置对象的对象头

6.执行init进行初始化(显示初始化)

7.string

1.string的不可变性

字符串创建后就不可改变,即使对它进行拼接等操作也是在新的字符串的基础上做的

2.string创建的位置

通过字面量给字符串赋值,此时字符串值声明在字符串常量池。(字符串常量池不会存储相同的字符串——底层使用map)

(1)String str1= “abc”; 在编译期,JVM会去常量池来查找是否存在“abc”,如果不存在,就在常量池中开辟一个空间来存储“abc”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。

(2)String str2 = new String("abc") ;在编译阶段JVM先去常量池中查找是否存在“abc”,如果过不存在,则在常量池中开辟一个空间存储“abc”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“abc”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。

3.string拼接操作

1.常量与常量的拼接结果在常量池,原理是编译期优化

String s1 = "a" + "b" + "c";
String s2 = "abc"
s1 == s2 //true

2.常量池中不会存在相同内容的常量

3.只要有一个是变量,结果就在堆中(相当于new String)。变量拼接的原理是stringBuilder

String s1 = "a";
String s2 ="b"; String s3 = "a" + s2;
String s4 = s1 + s2;
String s5 = "a" + "b" s3 == s4 //false
s4 == s5 //false String s6 = s3.intern()
s6 == s5 //true

4.拼接结果调用intern()方法,则主动将常量池中还没有的对象放入池中。并返回此对象地址(常量池中的地址)

4.拼接操作与append效率对比?

(1)
String s = "";
Random r = new Random();
for (int i = 0;i < 10;i++) {
s = s + r.nextInt(1000) + " ";
}
(2)
String s = "";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 10; i++){
sb.append(" ");
}

变量的字符串拼接,底层调用的是stringBuilder的append方法。在for循环中这样拼接的话每次都创建一个stringBuilder对象

5.new string创建对象的个数?

1.new string("ab");会创建几个对象?

两个,堆空间+字符串常量池

2.new String("a") + new String("b")创建几个对象?

(1)StringBuilder

(2)new string("a")

(3)常量池中的"a"

(4)new string("b")

(5)常量池中的"b"

四.执行引擎

1.执行引擎概述

将字节码指令解释/编译(注意区分和前面java的编译)为对应平台上的本地机器指令

2.解释器和编译器

1.解释器:对字节码采用逐行解释的方式执行,将字节码指令解释为本地本地机器指令

2.编译器:将Java编译为class代码

五.对象存活判断(标记阶段)

1.引用计数算法

1.概述

每个对象关联一个引用计数器属性,任何一个对象引用了A,引用计数器的值加1.当引用失效时,引用计数器就减1.当引用计数器的值为0时,表示对象不再被使用,可进行回收

2.缺点

1.需要单独的字段存储计数器,这样增加了存储空间的开销

2.每次赋值都要更新计数器值,增加了时间开销

3.存在循环引用的问题(所以jvm不用)

`

public class TestClass {

private Object ref;

public static void main(String[] args) {

    TestClass o1 = new TestClass(); // 1
TestClass o2 = new TestClass(); // 2
o1.ref = o2; // 3
o2.ref = o1; // 4
o1 = null; // 5
o2 = null; // 6
}

}

`

2.可达性分析法

1.概念

设立若干根对象,当任何一个根对象到某一个对象均不可达时,认为这个对象可以被回收

2.哪些可以作为gc roots?

1.虚拟机栈(栈帧中的本地变量表)中引用的对象;

2.本地方法栈中JNI(即一般说的Native方法)中引用的对象

3.方法区中的类静态属性引用的对象;

4.方法区中常量引用的对象;

(GC Root Object可以从Java堆的外部访问,也就是不受GC的自动回收管制。可以理解为有免死金牌的Java对象)

六.垃圾清除算法(清除阶段)

1.标记-清除算法

1.概述

标记-清除算法:通过根节点,标记所有根节点开始的可达对象,清除未被标记对象(会产生内存碎片)

2.缺点

1.stop the world,gc的时候停止整个应用程序

2.产生碎片,需要维护一个空闲列表(记录垃圾所在的位置,新对象到时可以直接覆盖该区域)

3.效率不高(进行了两次O(N)的遍历)

2.复制算法(新生代默认)

1.概述

将内存分为一块较大的Eden和两块较小的survivor,每次使用Eden和其中一块survivor。Gc时将标记的对象复制到另一块survivor

1.优点

1.没有标记和清除的过程,运行高效

2.复制过去以后保证空间的连续性,不会有碎片产生

2.缺点

浪费内存空间,始终要有一个空闲的survivor

3.标记-整理算法

1.概述

将标记的对象移动到内存的一端,清除边界外的所有空间(解决了碎片问题)

4.三种算法的对比

速度:复制>标记清除>标记整理

5.分代收集算法

目前几乎所有的gc采用分代收集算法进行垃圾回收

年轻代:复制算法

老年代:标记清除与标记整理混合

七.垃圾收集器

新生代收集器:

serial

pranew

parallel scavenge

老年代收集器:

serial old

parallel old

cms

整堆收集器:

G1

1.serial

新生代单线程收集器

采用复制算法

stw

2.pranew

新生代并行收集器

采用复制算法

stw

3.parallel scavenge

并行收集器,追求高吞吐(代码运行时间/(代码运行时间+垃圾收集时间))

采用复制算法

stw

4.serial old

serial的老年代版本

采用标记整理算法

一般是作为CMS收集器的后备预案,在并发收集发生concurrent mode failure时使用

(concurrent mode failure是在执行CMS GC(并发标记/清除)的过程中同时有对象要放入老年代,而此时老年代空间不足造成的,此时要进行full gc)

5.parallel old

parallel scavenge的老年代版本

6.cms

以获得最短停顿时间为目的的收集器

标记-清除

收集步骤:

(1)初始标记:仅标记gc root直接关联的对象

(2)并发标记:标记所有gc root关联的对象

(3)重新标记:标记并发阶段发生变化的对象

(4)并发清除:回收所有的垃圾对象

缺点:

(1)cms无法处理浮动垃圾(并发gc过程中用户线程产生的垃圾)导致,concurrent mode failure,从而产生full gc

(2)基于标记-清除,产生大量碎片

7.G1

新生代和老年代收集器,把堆内存划分为不同的region。来代替eden,s0,s1,老年代等。跟踪各个region的垃圾价值(垃圾的数量和垃圾收集的时间)。维护一个优先级列表,优先收集价值最大的region。避免了堆中全区域的垃圾收集

采用标记-整理 算法

jdk1.7后全新的回收器,用于取代cms收集器

收集步骤:

(1)初始标记:仅标记gc root直接关联的对象

(2)并发标记:标记所有gc root关联的对象

(3)重新标记:标记并发阶段发生变化的对象

(4)筛选回收:根据优先级,对region进行回收

【总结】jvm的更多相关文章

  1. 46张PPT讲述JVM体系结构、GC算法和调优

    本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.(内嵌iframe,建议使用电脑浏览) 好东西当然要分享,PPT已上传可供下载 ...

  2. java 利用ManagementFactory获取jvm,os的一些信息--转

    原文地址:http://blog.csdn.net/dream_broken/article/details/49759043 想了解下某个Java项目的运行时jvm的情况,可以使用一些监控工具,比如 ...

  3. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  4. JVM类加载

    JVM的类加载机制就是:JVM把描述类的class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型 ClassLoader JVM中的ClassLoade ...

  5. Java虚拟机 JVM

    finalize();(不建议使用,代价高,不确定性大) 如果你在一个类中覆写了finalize()方法, 那么你可以在第一次被GC的时候,挽救一个你想挽救的对象,让其不被回收,但只能挽救一次. GC ...

  6. 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制

    参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...

  7. MapReduce剖析笔记之六:TaskTracker初始化任务并启动JVM过程

    在上面一节我们分析了JobTracker调用JobQueueTaskScheduler进行任务分配,JobQueueTaskScheduler又调用JobInProgress按照一定顺序查找任务的流程 ...

  8. java太low,又舍不得jvm平台的丰富资源?试试kotlin吧(一)

    尝试kotlin的起因 因为各种原因(版权,人员招聘),公司的技术体系从c#转到了java,我花了大概两周的时间来上手java,发现java的语法还是非常简单的,基本看着代码就知道什么意思.学习jav ...

  9. Jvm --- 常用工具

    jps:虚拟机进程状况工具 JVM Process Status Tool. 可以列出所有目前正在运行虚拟机的进程. jps -l 详细参数: -q 输出LVMID,省略主类名称 -m 输出虚拟机进程 ...

  10. JVM虚拟机结构

    JVM的主要结构如下图所示,图片引用自舒の随想日记. 方法区和堆由所有线程共享,其他区域都是线程私有的 程序计数器(Program Counter Register) 类似于PC寄存器,是一块较小的内 ...

随机推荐

  1. google chrome安装非官方市场插件方法

    1. 下载 Chrome组策略管理模板"chrome.adm"(下载地址):2. Win+R 打开运行,输入 gpedit.msc,确定:3. 依次展开 本地计算机策略 > ...

  2. Java环境变量配置 新手必备

    第一步:安装JDK,无脑下一步 建议修改安装路径 这里以jdk1.7为例子(之前帮机房安装软件,五六十台电脑都要用1.7); 2.安装完了之后右击此电脑,打开属性 打开系统高级设置 打开环境变量 这里 ...

  3. 花时三月 终于Spring Boot 微信点餐开源系统! 附源码

    架构 前后端分离:             Nginx与Tomcat的关系在这篇文章,几分钟可以快速了解: https://www.jianshu.com/p/22dcb7ef9172 补充: set ...

  4. spark textfile rdd 日记

    批量处理模板方法, 核心处理方法为内部方法 def batchProces(sc: SparkContext, locationFlag: Int, minid: Int, maxid: Int, n ...

  5. 无所不能的embedding 3. word2vec->Doc2vec[PV-DM/PV-DBOW]

    这一节我们来聊聊不定长的文本向量,这里我们暂不考虑有监督模型,也就是任务相关的句子表征,只看通用文本向量,根据文本长短有叫sentence2vec, paragraph2vec也有叫doc2vec的. ...

  6. Java知识系统回顾整理01基础06数组05复制数组

    数组的长度是不可变的,一旦分配好空间,是多长,就多长,不能增加也不能减少 一.复制数组 把一个数组的值,复制到另一个数组中 System.arraycopy(src, srcPos, dest, de ...

  7. Doug Lea在J.U.C包里面写的BUG又被网友发现了

    这是why的第 69 篇原创文章 BUG描述 一个编号为 8073704 的 JDK BUG,将串联起我的这篇文章. 也就是下面的这个链接. https://bugs.openjdk.java.net ...

  8. python中remove函数的坑

    摘要:对于python中的remove()函数,官方文档的解释是:Remove first occurrence of value.大意也就是移除列表中等于指定值的第一个匹配的元素. 常见用法: a ...

  9. 同一台电脑同时使用gitHub和gitLab

    工作中我们有时可能会在同一台电脑上使用多个git账号,例如:公司的gitLab账号,个人的gitHub账号.怎样才能在使用gitlab与github时,切换成对应的账号,并且免密?这时我们需要使用ss ...

  10. spring-boot-route(十五)整合RocketMQ

    RocketMQ简介 RocketMQ是阿里巴巴开源的消息中间件.目前已经贡献给Apache软件基金会,成为Apache的顶级项目. rocketMQ基本概念 1. Producer Group 生产 ...