JVM部分

JVM部分

##运行时数据区:

线程私有部分:三个

1.程序计数器:可看作当前线程所执行的字节码的行号指示器。
字节码解释器通过便便这个计数器的值来完成分支,循环、跳转、异常处理、线程恢复操作。

2.Java虚拟机栈(大部分所说的栈):
生命周期:同线程
每个方法产生栈帧:存放存储局部变量表、操作数栈、动态链接、方法出口。
存储局部变量表:各种基本数据类型,对象引用,returnAddress地址。

3.本地方法栈:
与虚拟机栈区别:虚拟机栈为Java方法(字节码)服务。本地方法栈为虚拟机用到的Native方法服务。

线程共享部分:三个

1.Java堆
特点:内存最大,可不连续,可选定大小,可扩展。线程共享。

唯一目的:存放对象实例及数组。(非绝对:有栈上分配和标量替换技术)

区域细分:新生代、老年代。或是Eden空间、From Survivor空间、To Survivor空间。

2.方法区
特点:线程共享。内存可不连续,可选定大小,可扩展。
不完全等同“永久代”。
目的:存放已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码。
2.1 运行时常量池(方法区一部分)

Class文件的具体组成结构:类的版本、字段、方法、接口,常量池。常量池存放编译期生成的各种字面量和符号引用。在类加载后进入运行时常量池。
对比其他Class文件组成,它的重要特点:动态性。在运行时,也能添加到常量池。实例:String类的intern()方法

对象的创建(普通对象,不包括数组和Class对象)

###1.接收new指令,
检查指令参数在常量池是否有类的符号引用。(存不存在这个类),并检查类是否被加载、解析和初始化。没有则请看类的初始化

2.为新生对象分配内存。

内存分配方式(由Java堆是否规整绝对(收集器,GC算法),请看压缩整理部分):
1.指针碰撞假设此时堆中内存规整(无碎片),分界指针向空闲部分移动对象大小距离。
2.空闲列表JVM维护一个列表,记录空间位置。

线程安全问题解决方式:
1.同步处理————CAS机制
2.本地线程分配缓冲:先分块区域给不同线程,在各自空间创建,用完再考虑同步锁定。

设置对象必要信息

设置对象头内容:类信息,类元数据信息,对象哈希码,对象的GC分代年龄等
是否启用偏向锁。
接下来才是<init>方法,为对象设置各种属性

对象的内存布局,内容?

对象头:包括对象自身的运行时数据(哈希码、GC年龄、锁状态、偏向线程ID、偏向时间戳) 、 类型指针(指向哪个类)
实例数据:父类继承的、子类定义的。
填充对齐:占位符作用,使对象大小必须是8字节的整数倍。

对象的访问与定位是怎样的?

此处输入图片的描述

使用句柄:堆中有个句柄池,栈的reference指向句柄地址,
特点:稳定,移动时只改句柄
直接指针:堆对象内部有个对象类型数据指针。
特点: 快,一次引用

异常:OutOfMemoryError 精简版本

1.堆溢出

区分:内存泄漏和内存溢出
内存泄漏memory leak:程序在申请内存后,无法释放已申请的内存空间(游离)
解决:查看泄露对象到GC ROOTS 的引用链,造成的无法GC

内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

2.虚拟机栈和本地方法栈溢出
1.一般是StackOverFlowError

有趣的现象:栈分配内存越大,越容易产生内存溢出。
原因:内存固定,每个栈的分配内存越大,总共的栈数(线程数)就越少。在多线程时更容易溢出。

第三章 垃圾收集器和内存分配策略

对象已死吗?是否可回收?

1.引用计数算法:添加一个计数器,被引用一次值+1
存在问题:循环引用问题,
2.可达性分析算法:
此处输入图片的描述
原理:从GC ROOTS 开始向下通过引用链搜索,判断是否可达。
GC ROOTS: 虚拟机栈中(栈帧中的本地变量表)引用的对象。
方法区中类static属性引用的 对象
方法区中常量引用的对象
本地方法栈中 Native方法引用的对象

引用的类型

引用如果只是 0 1 那未免太绝对,故存在四种等级:强软弱虚

强引用:代码之中普遍存在的。用 “=” 连接实例的,绝不会被回收
软引用:有用但非必须的对象。只有在内存溢出异常前才会加入回收。
弱引用:非必须,下次GC到来之时会回收。有内存时只回收弱引用。
虚引用:不影响对象生命周期,只作为系统通知而存在。

leave or die

必死对象:不可达对象会被标记(下通缉令),若对象没重写finalize()方法(免死金牌?),或者免死金牌用过了。 那么请die吧。

非必死对象:有没用过的免死金牌,对象放到F-Queue中,等待Finalizer线程去执行,免死金牌finalizer要使用得当(重新与GC root建立连接),并第二次标记(解除通缉令),如果使用不当,那么还是得over。

方法区(永久代)的回收————条件严苛

对象:废弃常量和无用的类
废弃常量:String s = “abc”,当没有引用的时候,回收
无用的类(需同时满足):1.该类所有实例被回收 2.该类的ClassLoader已被回收
3.该类对应的Class对象没有被引用,无法反射访问

垃圾收集算法

一、标记-清除算法(Mark-Sweep)算法
原理:可达性分析,标记清除对象,清除。
缺点:1.效率低下。 2.产生大量不连续的内存碎片,浪费空间

二、复制算法(用于新生代)
原理:将内存二分,每次用一块,不够用了就复制 有用对象 到另一块。然后本块全部清除。
优点:分配内存方便,直接用指针碰撞。
缺点:1.内存缩为一般。
应用:Eden和From Survivor空间的有用对象 复制到To Survivor空间,然后清除掉自生空间。E : FS : TS = 8 : 1 : :1
所以可用空间有9。

三、标记—整理算法(Mark-Compact)(年老代)
原理:标记完,不是清除,而是整理,存活对象都向一端移动。清除边界外的对象。

四、目前商用————分代收集算法
新生代(大量对象死去)用复制算法,年老代(存活率高)用标记—整理算法(或者标记—清除算法)

HotSpot的算法实现

枚举根节点:为了快速检查GC ROOTS的引用,采用了:OopMap数据结构,在类加载完成时,会记录下引用。
安全点: hotspot在线程达到安全点的时候才会考虑一次OopMap,并且开始GC。
问题:若多线程下不在安全点怎么办? 1.抢先式中断(没JVM在用):停下所有线程,若发现线程不在安全点上,则恢复让其达到安全点并停止。
2主动式中断:设置一个标志,让各线程去轮询这个标志,发现中断标志后就自己中断挂起。轮询标志与安全点位置重合。

多种GC收集器(仅讨论HotSpot的)

连线是配合
连线表示了两种收集器能否配合使用。

新生代:
1.Serial收集器:单线程,GC时暂停所有工作线程
2.parNew收集器:上述的多线程版本,其他区别不大
3.Parallel Scavaenge:平行扫描,新生代并行回收(无法配合
老年代:
1.Serial Old:那家伙的老年代版本,使用标记—整理算法
2.parNew Old:多线程,标记—整理算法,只能配合上面的3
3.CMS(Concurrent Mark Sweep)收集器:并发收集,低停顿。
缺点:1.对CPU资源敏感2.无法处理浮动垃圾3.属于清除算法,会产生碎片

G1收集器 1.7开始
特点:
1.并行与并发:利用CPU、多核来减少StopTheWorld的时间
2.分代收集:可独立管理GC堆,不用其他收集器
3.空间整合:整体基于清除-整理算法,不同于CMS的清理。
4.可预测的停顿:可建立可预测的停顿模型

一图搞懂内存分配与回收策略

此处输入图片的描述

第七章 虚拟机类加载机制

类的生命周期七阶段

加载、验证、准备、解析、初始化、使用、卸载

什么会触发加载:
1)遇到new(实例对象化)、getstatic(读取静态字段)、putstatic(设置静态字段)、invokestatic(调用静态方法)字节码时。
2)使用java.lang.reflect包的方法对类反射调用时
3)初始化一个类必须先初始化其父类
4)虚拟机启动,初始化主类。包含main()的类
5)java.lang,invoke.MethonHandle实例的方法句柄(这是个啥???)

数组有虚拟机直接创建,不通过类加载器

类加载过程

1.加载:虚拟机 :类名→二进制字节流→静态结构变运行数据结构→Class对象作为入口
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态储存结构转化为方法区运行时数据结构,并储存在方法区。
3)在内存中产生这个类的java.lang.Class对象,储存在方法区,作为方法区的这个类的各种资源访问入口

2.验证:格式、元数据、字节码、符号
确保Class文件的字节流符合当前虚拟机要求,防止有害字节流导致系统崩溃。会验证:
1)文件格式 (开头,版本,类型)2)元数据信息(父类,继承是否符合规定,是否矛盾)3)字节码(类型转换有效)4)符号引用

3.准备: 类变量分配内存
类变量分配内存 并设置 类变量初始值的阶段,类变量也就是static变量都在方法区进行分配。不包括实例变量(只在对象实例化的时候随对象分配在Java堆中)

1
public static int value = 123;

此时只会赋0值,因为复制的putStatic指令是编译后,存放在类构造器<client>()方法中,在初始化阶段才会进行。

4.解析:将常量值内符号引用转化为直接引用
类或接口的解析,字段解析,类方法解析,接口方法解析。

5.初始化 为静态属性初始化值
前面均有虚拟机来完成,初始化才开始执行类中定义的字节码。执行类构造器<clinit>()方法,从java.oang.Object开始依次向下执行。由父到子。

类加载器 作用

1.加载 类

  1. 判别类,同类是由同一个类加载器加载出来的,比如同Class文件,同JVM,而不同类加载器。出来的类是不相等的

你可以手写一个简单的类加载器,并加载一个Class文件,然后判断加载出来的类与系统加载出来的类是否相同,答案当然是不同的!!

双亲委派模型

类加载器 从 JVM角度 分类:
1.启动类加载器(JVM的一部分)
2.其他类加载器(独立于JVM,继承抽象类java.lang.ClassLoader)

细分:

  • 启动类加载器:加载\lib中的类库到虚拟机内存中,无法被Java程序直接引用。
  • 扩展类加载器:加载\lib\ext目录中的,或者被指定路径中的所有类库
  • 应用程序类加载器:由ClassLoader实现,ClassLoader.getSystemClassLoader(),负责ClassPath上所制定的类库。若没有自定义类加载器,一般这个就是程序默认的加载器

双亲委派模型

要求:除了顶层的启动类加载器外,其余的类加载器都用当有自己的父类加载器,这里不以继承关系而由组合实现复用父类加载器代码。

工作过程:
1.类加载器 收到 加载类请求,先委派给 父类加载器完成
2.所以,所有类都是先由 启动类加载器 先尝试加载
3.当前类加载器无法完成加载(没找到所需的类),交由子加载器加载。

优点:使Java类随类加载器一起具备了带有优先级的层次关系。如java.lang.Object。他存放在rt.jar中。所以无论哪个加载器加载Object类,都会委托给启动类加载器加载,保证了Object唯一性。以免用户自己写一个Object类,避免混乱,你若自己写一个,你会发现可以正常编译,但无法被加载运行。

逻辑:
1.先检查是否加载,没加载调用父类加载器的LoadClass(),若父类为空则调用启动类加载器。若加载失败,抛出ClassNotFoundException后调用自生的findClass()加载

此处输入图片的描述

破坏双亲委派模型

双亲是推荐加载机制而非强制。曾有三次破坏。

1.为了向前兼容JDK1.2以前的自定义类加载器,添加了一个findClass()方法,(这里有点晕)

2.模型的缺陷导致:若基础类(本由启动类加载器加载)要调用回用户代码(由应用程序加载器加载)咋办(启动类加载器不认ClassPath路径)。
比如:JNDI(命名和目录接口) 服务,目的是对资源进行集中和查找,需要调用 JNDI接口提供者的代码。

不优雅的设计:线程上下文类加载器,线程若未设置则从父类继承,全局都没有则默认为应用程序类加载器。
使用:基础类程序(比如JNDI)可以使用这个加载器(未设定时为应用程序加载器)加载所需要的SPI代码,也就是父类加载器请亲子类加载器去完成类加载。
在JNDI、JDBC 、JCE、JAXB和JBI

3.为了追求程序动态性:代码热替换(不关机的替换),模块热部署。

JSR-291(Java 规范提案) (即OSGi R4.2)时目前Java模块化的标准,实现模块化的热部署的关键是其自定义类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器。更换bundle时连同类加载器一起替换掉以实现热替换。

此时,类加载器不是双亲委派的树结构,而是网结构(即有双亲委派模式,又有平行的类加载器)

高效并发

Java内存模型

用处:屏蔽各种硬件和操作系统的内存访问差异,让Java程序达到一致性。
此处输入图片的描述
主内存:主要对应堆中对象实例数据部分。
工作内存:对应虚拟机栈的部分区域。
虚拟机回让工作内存有限存储于 寄存器和高速缓存中,因为主要访问读写的时工作内存。

volatile型变量

轻量级同步机制,可保证可见性但是不能保证原子性。其在各个线程的工作内存中不存在一致性问题(在线程中不同,但是每次使用前要刷新)。但是不能保证原子性。

Java线程调度

指的是系统为线程分配处理器使用权的过程。
1.协同式调度:执行时间由线程本身控制。执行完自己后再通知系统切换
好处:实现简单,操作可知。
坏处:线程执行时间不可控,阻塞问题难以解决

2.抢占式调度:执行时间由系统来分配,线程的切换由系统控制,可以辅助设置线程优先级别,越高越易被系统执行。

第13章 线程安全与锁优化

定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行格外的同步,或者在调用方法进行任何其他的协调操作,都能得到正确的结果,那么是线程安全的。

线程安全的实现方法

1.互斥同步
synchronized:JVM层面,经过编译后会在同步块前后产生monitorEnter和monitorExit两个字节码指令。根据虚拟机规范执行此Enter指令时,首先获取对象锁,获取到则计数器由0变1,获取失败则线程阻塞。
阻塞和唤醒一个线程,需要操作系统来帮忙,从用户态转换到核心态,需要消耗很多的处理器时间,所以synchronized是一个重量级的锁
ReentrantLock 三个新特性
等待可中断:lock可设置等待参数,没获取到则放弃获取
可实现公平锁:同一个锁,线程按照排队顺序来获取。ReentrantLock虽然也是非公平,但是可以通过带布尔的构造函数显示公平
可绑定多条件:

Class文件的具体组成结构:类的版本、字段、方法、接口,常量池。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2019 Jae's blog All Rights Reserved.

UV : | PV :