一.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. tf.split函数的用法(tensorflow1.13.0)

    tf.split(input, num_split, dimension): dimension指输入张量的哪一个维度,如果是0就表示对第0维度进行切割:num_split就是切割的数量,如果是2就表 ...

  2. SpringBoot+RabbitMQ 方式收发消息

    本篇会和SpringBoot做整合,采用自动配置的方式进行开发,我们只需要声明RabbitMQ地址就可以了,关于各种创建连接关闭连接的事都由Spring帮我们了~ 交给Spring帮我们管理连接可以让 ...

  3. Git源代码管理笔记

    很早之前也学过git但是没有详细的记录,这次是做个复习,巩固 若有不对的地方请您指出 目录 Git和SVN有什么区别? 什么是Git? github仓库 Star和Fork Pull request ...

  4. 前端er,你真的会用 async 吗?

    async 异步函数 不完全使用攻略 前言 现在已经到 8012 年的尾声了,前端各方面的技术发展也层出不穷,VueConf TO 2018 大会 也发布了 Vue 3.0的计划.而在我们(我)的日常 ...

  5. Java基础——消息队列

    1.消息队列的适用场景:商品秒杀.系统解耦.日志记录等 2.使用Queue实现消息对列 双端队列(Deque)是 Queue 的子类也是 Queue 的补充类,头部和尾部都支持元素插入和获取阻塞队列指 ...

  6. 046 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 08 for循环的注意事项

    046 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 08 for循环的注意事项 本文知识点:for循环的注意事项 for循环的注意事项 for循环有3个 ...

  7. [学习笔记] 树上倍增求LCA

    倍增这种东西,听起来挺高级,其实功能还没有线段树强大.线段树支持修改.查询,而倍增却不能支持修改,但是代码比线段树简单得多,而且当倍增这种思想被应用到树上时,它的价值就跟坐火箭一样,噌噌噌地往上涨. ...

  8. FRP服务

    FRP服务 - Web服务 本服务提供Web内网穿透服务,并且开放端口 443和 80端口. 写在前面:提供公益FRP服务器:frp.dev.boselor.com,服务器在洛杉矶但是请勿用于违法用途 ...

  9. k8s集群,使用pvc方式实现数据持久化存储

    环境: 系统 华为openEulerOS(CentOS7) k8s版本 1.17.3 master 192.168.1.244 node1 192.168.1.245 介绍: 在Kubernetes中 ...

  10. day33 Pyhton logging 模块和包

    一内容回顾 try/except try/except/else try/finally try/except try/except/finally try/except/else/finally 报 ...