2018-05-15 -> 2018-07-12

1 hello world

书:《深入理解java虚拟机》第二版

2 java内存区域与内存溢出异常

java自动管理内存释放

运行时 数据区

  • 方法区 method area// 存储已被虚拟机加载的类信息 常量 静态变可以看做堆的逻辑部分 别名non-heap

  • 虚拟机栈VM stack // 线程私有 java 方法服务

  • 本地方法栈 native method stack// 虚拟机用到的native 方法服务

  • 堆heap// 几乎所有对象实例

  • 程序计数器 Program counter register// 这个java 模拟出类似汇编pc的一个自己字节码的pcr//当前在执行字节码时 有指向,执行机器码时指向空

  • 运行时常量池 //方法区一部分 编译时期的字面量和符号引用

数据区以外

  • 直接内存,JDK中NIO库,以及相关操作方法,相当于更好的控制内存了???

2.3.1 对象

new

  • 去常量池中找类的符号引用,检查是否被加载解析 初始化过(7章)
  • 从堆上新生对象分配内存//赋值0 虚拟机中额外的标识操作 markword 比如哈希、GC分代年龄、元数据、偏向线程ID, init()

访问

  • 通过栈上记录对对象的引用->句柄池(到对象实例数据的指针 到对象类型数据的指针)/到对象实例的指针 对象示例数据->实例

outofmemory实战的VM参数-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,前面参数是设置初始化和上限大小,后面是帮助dump

可以用相应IDE的工具来查看GC Roots的引用链

String.intern() 是native方法,如果字符串常量池中已经包含一个等于此String 对象的字符串,则返回代表池中这个字符串的String对象,否则将此String 对象所包含的字符串添加到常量池中,并且放回此string对象的引用// 1.6->1.7版本更换时的改动

方法区溢出,类的回收判定咳咳,大量jsp或动态产生JSP文件的应用

3 垃圾收集器GC与内存分配策略

  • 哪些内容需要回收
  • 什么时候回收
  • 如何回收

对象死亡判断–引用计数算法,引用+1,引用失效-1,简单 判定效率高,大部分情况下不错,但无法解决循环引用的问题

example: struct s{s * next};s* a,s* b;a->next=&b,b->next=&a;a=NULL,b=NULL;

VM 参数-verbose:gc -Xmx20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

可达性算法,通过GC Roots 向下搜索(引用链),java中可以作为GC Root的对象包括

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(native方法)引用的对象

引用的定义

  • 强引用(Object obj = new Object())
  • 软引用(将要溢出时回收)
  • 弱引用(回收时 销毁)
  • 虚引用(用来作为被回收时的通知用的)

没有覆盖finalize或finalize已经被调用过(也就是每个实例的finalize只会执行一次 所以也只有一个自救的机会),则没有必要执行。所以finalize()时是最后一次逃脱销毁的时刻。然而笔者并不鼓励用这个方法,建议大奖忘掉,try-finally或其它方法可以做得更好

回收方法区

方法区(hotspot中的永久代)也有垃圾回收(主要是废弃常量 无用的类),但性价比低。新生代中垃圾回收可以回收75-90%的空间,

废弃常量比较容易判断

无用的类 回收的前提条件(也只是 可以 回收,和对象不同 对象不使用必然回首):该类的所有实例被回收,加载该类的ClassLoader已经被回收,该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

-verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnLoading

垃圾收集算法

标记清除算法(mark-sweep):最基本的 也就是 标记一轮,收集一轮,后面的基于该算法改进

复制算法copying,内存化为两半A、B,只用A,标记,收集把剩余的移动到B,只用B,标记B,收集把剩余的移动到A,代价是一半的内存,解决的是空闲碎片问题

  • 商业VM中采用这个方法来回收新生代,而IBM研究表明,98%的新生代 朝生夕死,所以并不需要1:1的分配,而是用一个大的Eden和两个小的survivor大约8:1:1的分类,也就是每次能用90%完整内存

标记整理算法,标记,收集并整理,和复制不同的是不用浪费空间,而且相对来说 如果存活率高的化,这样的代价相对少,用于老年代

分代收集=按需具体新生代、老年代不同的手机算法。

HotSpot 算法实现

GC发生时需要停顿所有JAVA线程执行,所以从GC roots的扫描要快,HotSpot通过OopMap的数据结构,在类加载完成的时候就把对象内的偏移量对应的数据类型计算出,在JIT编译过程中也会记录栈和寄存器中哪些是引用,这样扫描就直接有信息了

安全点:如果每条指令都生成对应OopMap将会要大量额外空间,所以只会在safepoint才会暂停开始GC,例如方法调用,循环跳转,异常跳转,并且让所有线程都运行到最近的安全点停下来,

  • 抢先式中断,让没跑到safepoint继续跑,已经到了的等待,,,,几乎没有VM这样用
  • 主动式中断,每个线程都去找GC的中断标志,在每次到safepoint时检查标志

然而可能有线程sleep或blocked【也就是不执行】,需要安全区域(safe region)来解决,所谓安全区域是只一个 引用关系没有变化的区域,在线程离开safe region时,检查是否GC完成,没有则等待,

垃圾收集器

Yound generation/ tenured generation

7种

  • serial(年轻代) 单线程收集器->所有线程到safepoint->”stop the world”->GC ,在client模式下的VM中的新生代依然是一个好的选择
  • ParNew(年轻代) serial的多线程版本,和上面不同的是 GC的过程是多线程收集,server模式下新生代首选, 只有它目前能和CMS配合工作
  • Parallel Scavenge 是新生代收集器,复制算法 并行多线程手机,主要特点在于可控 CPU使用的吞吐量,能够动态根据吞吐量需求调整 大小
  • serial old 老年代版本 标记整理 主要是client模式下vm用
  • parallel old 老年代 标记整理,说这个配合paralle scavenge使用
  • CMS 收集器 以获取最短回收停顿为目标的收集器,(Mark Sweep) 标记-清除算法
  • G1 当今最前沿的成果之一 直到jdk7u4认为它达到足够成熟的商用程度

CMS:

  • 初始标记 记录GC Roots能够直接关联道德对象 很快 // 需要stop the world
  • 并发标记 gc root tracing
  • 重标记 remark 【解决运行时产生的变动的部分?】 // 需要stop the world
  • 并发清除

对CPU数量有要求,并发的定义适合用户线程,然后并行的并不是等到 空间使用完了才收集,而是在过程中 书上说68%老年代的会出现收集,,jdk1.6启动阈值92%,另外一个是碎片合并的问题,可以参数控制

G1:

  • 并发并行,利用多CPU,缩短stop-the-world
  • 分带收集,能够独立管理所有代的 GC堆
  • 空间整合,从整体看是 标记–整理,局部看是复制算法,不会有碎片
  • 相对于CMS除了降低停顿时间,还能建立可预测的 停顿时间模型

G1之前 其它收集器进行搜收集范围是整个新生代或者老年代。G1将堆划分为多个大小相等的独立Region,然后手机过程不是整个新生代或老年代,而是根据每个Region的垃圾堆积的价值大小,说着只是划分而已然而实现并不简单2004年就发了论文,然后10年时间才开发出商用版,

回收步骤:

  • 初始标记
  • 并发标记
  • 最终标记 意思相当于上面的remark 并行,stop-the-world
  • 筛选回收 并行

理解GC日志

学会如何阅读GC日志,-XX:+PrintGCDetails,这个demo很快理解了,然后具体要用再查就OK

总结? 垃圾回收这一块 jvm也是带了很多参数可以在运行时使用, 分配会一定程度和GC收集器相关、受到影响

Eden?? TODO

大多情况,对象优先在Eden区域分配,当 Eden区域没有足够空间时,虚拟机将发起一次Minor GC

书上给了例子,4次分配,限制总空间和新生代空间,前三次能满足新生代空间,但第四次不能满足,同时vm没有找到可以回收的对象,为了满足分配保证机制,把前三次的送入老年代,然后第四次的在新生代分配,

可以设置大对象阈值,超过该阈值的直接进入老年代

Eden出生一次Minor到Survivor中,之后一次Minor GC年龄增长一岁,设置年龄界限参数,,然后还有一个同年龄的对象空间占用和达到一半会分入老年代的规则

担保分配过程 -> 一点统计参数+找可用剩余空间

4 章 虚拟机性能监控与故障处理工具

介绍jdk除了java和javac的其它工具,jdk1.5以下要专门加参数开启

可以搜SUN JDK 监控和故障处理工具命令行工具:

名称 主要作用
jps jvm process status tool,显示指定系统内所有的hotspot虚拟机进程
jstat jvm statistics monitoring tool,用于收集hotspot虚拟机各方面的运行数据
jinfo configuration info for java,显示虚拟机配置信息
jmap memory map for java,生成虚拟机的内存转储快照(heapdump文件)
jhat jvm heap dump browser,用于分析heapmap文件,它会建立一个http/html服务器 让用户可以在浏览器上查看分析结果
jstack stack trace for java ,显示虚拟机的线程快照

HSDIS JIT生成反汇编 =。= 哇哦 毕竟生成java可执行码直接看不懂,而汇编相对玩过C++和汇编的来说就容易看懂了

可视化工具:jconsole //可以通过线程观察等待的函数 甚至一些可以判定的死锁

//运行我写的一个项目然后是Eden在产生和被回收,但是survivor一直是100% 没有进入老年代的为何? 放久了有部分进入

visualVM 官方提供 的可视化可插件的

  • heap dump
  • Btrace动态日志跟踪 插入代码

5 章 调优案例分析与实战

用案例说明

案例1

从32位低硬件转移到高硬件,就是一个高访问,然后要从磁盘读数据的,分配了12GB/16GB的,而一次Full GC(同时回收年轻代 年老代,永久代满会导致class method元信息的卸载)需要14s的停顿,且频率十几分钟一次

文中举例64位的缺点:1.内存回收导致长时间停顿,无法dump【因为如果要dump 产生的文件大小能达到十多G 也无法分析,因为指针大小 对齐补白一般比32位内存使用更多

然后说更多的还是用32位虚拟机集群,缺点:1.注意并行资源竞争 2.均摊性能,3。32位虚拟内存上限,4.大量本地缓存

案例2

分布式 有一定共享的竞争型数据库,改用全局缓存后出现内存溢出问题。 memorydump出的数据显示很多NAKACK,这个是JBossCache保证各个包有效顺序及重发,也就是为了保证顺序性会在内存中保留并且网络不佳时会有大量重发,就会堆积以至溢出。【这个只是说了能分析到原因,没说解决方案,我猜大概 使用更大内存空间?

案例3

小的基于B/S电子考试系统,服务器不定时异常,但是把堆开到最大32位下1.6GB,也没有效果甚至异常更频繁了,然后加heapdump 没用,异常时没有dump文件,然后用jstat监视,gc不频繁,多个代都没压力。最后从系统日志中找到异常堆栈

Direct Memory不算如java的内存堆中,从输出的超过内存上界的报错看是direct 的allocate错误,虚拟机会对direct回收,但不会空间不足就触发像新生代老年代那样的回收,

socket 连接的receive 和send两个缓存区

案例4

外部命令导致系统缓慢,数字校园应用系统,并发压力测试时请求响应慢,CPU使用率高,,通过跟踪发现时linux的fork,而java中最多有线程产生没有进程产生,再溯源到Runtime.getRuntime().exec() ,,,解决方案是去掉该shell脚本 改为java api去获取信息。

案例5

服务器jvm进程崩溃,日志看是socke上http上的,问题溯源是 异步服务速度不对等 积累的等待线程socket

案例6

不恰当数据结构导致内存占用过大,感觉要对hashmap的实现了解

案例7

偶尔出现GC长达1min左右,但是gc的执行时间不长,但是准备时间长,然后监测到最小化时,资源管理中内存大幅度减少,最后断定是 windows把内存临时交换到磁盘里去了 加参数解决

实战调优

笔者用eclipse开发装了很多插件,启动要4-5min,全默认配置,作为对比,先统计了最初的启动 时间,垃圾回收耗时,full gc次数耗时,minor gc次数耗时,加载类,编译时间。

  • 升级jdk,升级jdk从宏观意义上统计意义上能带来一定的性能提升,这里1.5到1.6 update21上用力说是提升了15%,但是实际启动时间并没有效果

而且升级以后类加载时间更久了是原来的2倍,然后作者定位到???怎么定位,字节码验证部分耗时差距最大,然后加了一个禁用验证的参数,两个版本的时间才相近

取消验证后 原来15s的 1.5版本变为13s,1.6版本变为12s

然而升级以后 发生了内存溢出,visualvm检测到permgen的大小和最大值曲线重叠,解决方案是改配置文件的启动参数设置更大的永久代

这里的编译时间是指JIT HotSpot把java字节码编译为本地代码【但是如果把编译关了 换来的是执行时间更久

然后有不同优化程度的编译器,如果是长时间运行eclipse的话,那么用一个更多优化消耗更多编译时间的C2编译器,在运行速度中可以赚回来

最后优化GC,因为minor GC太频繁 耗时多,而minor GC会让多个线程跑到安全点等待回收,过多minor GC会导致不必要的挂起和恢复

minor通过参数增加eden大小,老年代回收日志显示 老年代在一次次扩容,然后 作者的做法一开始就设置好 减少自动扩展,然后这样设置以后 检测到的minor GC和full GC只有个位数次

然后观察到 老年代没有满过但还是有gc,,,原因是代码显式 System.gc(),通过参数禁用显式 GC,至此只需要7+秒

当前配置下eclipse进行代码编译中 老年代回收耗时长,CPU使用率最高才30%,通过参数指定 新生代和老年代的垃圾收集器,让老年代收集时间显著下降[实际上是CMS 和程序之间有并发时间 看上去停顿的时间降低显著]

该章总结 是通过不同部分运行日志 来分析运行的瓶颈,从而加参数修改。 知识依赖就是对 jvm运行时熟悉

6 类文件结构

代码编译结果 从 本地机器码 变为字节码?? 这说的不是物与物转换吧,是历史上的时间变为吧

【看了第一章 果然是时间上的 不是 物 转为 物

向java设计之初低头,说是最开始就拆分为 java语言规范 和 JVM规范,如现在有Groovy Scala等,大概就是 任何语言->编译器->.class字节码->在jvm上运行【突然感觉董哥之前的js on jvm好吊呀

以及 jvm 所支持的 大于 java所支持的

这里作者说有标准参考书《java 虚拟机规范(Java SE 7)》

二进制流,必要数据,无分隔符,类似C语言的伪结构体——无符号数和表

无符号数u1 u2 u4 u8,表_info格式结尾

类型的集合:同一类型但数量不定的多个数据时,前置容量计数器加若干个连续的数据项的形式

魔数与class文件版本

magic number,class文件头的4个字节,唯一作用确定该文件是否是一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来身份识别.

【例如gif jpeg文件头都有魔数

class的魔数为0xCAFEBABE

紧接着魔数的4个字节是版本号,我这边在linux下用vim+xxd可以看,想gui看的话可以ghex

[0123][45][67]
[魔数][次版本号][主版本号]

//即使代码没有变,低版本虚拟机也会拒绝高版本的class执行

再后面是常量池入口 显示u2类型 常量池容量计数值,从1开始计数,主要存放两大常量:字面量(文本字符串 声明 final 常量)和符号引用(类和接口的全限定名,字段的名称和描述符,方法名称和描述符),

然后常量池中每一个常量是个表,,类型挺多的。。到1.7有14种了?

[类型标识][按照类型的数据][类型标识][按照类型的数据]…

所以 方法 字段名会受到这里的限制,所以方法变量名 最大长度不能超过65535

然后有提到相应的工具 javap 带上-verbose参数,话说这个可以用来编译咯

其中可以看到 有部分常量没有在java代码中出现过,它们是自动生成的常量,如字段表 方法表 属性表

此处有表<常量池中的14种常量项的结构总表>

在常量池结束后是 两个字节访问标志(access_flags) 标识 类还是 接口 ,是否public ,是否被final,用的bit表示法,

类索引(this_class)父类索引(super_class)u2类型数据,接口索引集合(interfaces)一组u2类型的数据集合 描述具体实现了哪些类

除了java.lang.Object所有java类都有父类,

这两个索引用u2索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类型描述符常量,

查找到具体类的流程就是

this_class->CONSTANT_Class_info->CONSTANT_Utf8_info

【这一段将得好无聊啊23333,感觉都是按照 位置对应内容+(操作符+内容)的格式,

字段表集合,用于描述接口或者类中声明的变量。access_flags,name_index,descriptor_index,attributes_count,attributes

代码被编译存放在方法属性表集合中一个名为code的属性里面,,,然后java不能重载仅返回值不同的函数,但jvm标准的class文件中可以

属性方法表,code属性表

然后是javap 所得到的args_size 的值 在空时也为1,因为会隐式传入this

在字节码指令之后是 异常处理表集合, 格式 是 代码起始行 代码结束行 处理函数地址 要catch的类型

linenumbertable用于描述java源码行号与字节码行号之间的对应关系,可以用参数省去该表,但省去以后如果出现异常将无法直接定位源代码中所在行号

localvariableTable是描述栈帧中布局与原代码中变量之间关系,也是可以参数省去

以及sourceFile属性ConstantValue属性,InnerClasses,Deprecated 和Synthetic

这几页 感觉看了也记不住,而且也不可能说背,毕竟表结构和参数对应辣么多,但对大自轮廓有了印象

然后字节码简介 概括下来就是 基本仿照 真机器码的编码思想

作者对这些分类讲解。。。。。

局部变量加载到操作栈iload iload__n lload lload_n fload..

将一个数值从操作数栈存储到局部变量表,储存istore…lstore..

讲一个常量加载到操作数栈 bipush sipush ldc ldc_w ldc2_w aconst_null iconst_m1 iconst_i lconst_i

扩充局部变量访问索引指令wide

运算 基本按照 [变量类型][操作类型]来命名 ,比如 iadd isub ladd lsub

这里有个疑问它字节码有iinc 局部变量自增,那这个操作算原子的吗?

然后搜到说java中i++转换的字节码不是这个,然后我测了一下,局部变量还是会变成iinc的,但是全局的就不是,所以也就是说,iinc都只会在局部中出现咯,也就是并没有什么原子保证。

浮点数方面是IEEE 754,遵守浮点数值和逐级下溢运算规则,

转换指令 可以可以 向2=to,4=for低头 i2b,i2c等等

创建new[…] ,读取[…]load存储[…]store,

栈操作 pop… dup…复制+压入 swap

方法调用invoke[调用的配型] 返回[类型]return

看到这我还在想,为什么一个和寄存器有关的都没有,再想到java要一次编写,所以这是说靠jvm读字节码时再 和寄存器什么的相关咯

monitorenter和monitorexit支持,java中的synchronized同步

如果之前学过汇编,看这个就基本属于快速过掉,具体要查再查吧,这个的命名规则很是直接了,然后有部分 很明先汇编里没有的也大自看一看

7 虚拟机类加载机制

加载到内存 ,校验,转换解析和初始化,最终形成过程都是在程序呢运行期间完成的,

所以java是语言运行期类加载的特性

类生命周期

加载(Loading)->连接(Linking)(验证->准备->解析)->初始化->使用->卸载

规定:5种需要立即 初始化

  • new getstatic putstatic invokestatic字节码 且没有初始化时
  • java.lang.reflect 对类进行反射调用时 且没有初始化???????? 那这个对应的字节码呢 这划分得一脸懵逼,
  • 初始化类时,如果其父类没有初始化,需要先触发其父类的初始化
  • vm启动时,用户需要指定一个包含main方法的类,vm需要先初始话这个类,
  • 1.7版本的动态语言 中 REF_第一条 中的这些指令且没有初始化时

然后说有且只有这5种会,称作主动引用,其它是被动引用

静态字段只有直接定义这个字段的类才会被初始化

样例见书: 父亲类(初始化静态输出1,静态变量value),子类(初始化静态输出2), 调用样例输出 子类.value,因为value是父亲

惊了 我去测了一圈 然后才知道java里父类和子类的成员变量竟然都记录了的,比如A.x ,然后B是A的子类,有B.x,当你把一个B转化成A以后访问x只能访问到A.x,但是如果是方法的话,就算把B转成A访问还是会调用B.x,相当于C++中的自带virtual了

数组定义引用类不会触发, A[] arrA = new A[233];

final static被常量传播优化于编译阶段时,不会触发类的初始化

类加载过程

加载 验证 准备 解析 初始化 这5个阶段

加载

  • 通过类全限定名获取定义此类的二进制流
  • 将所代表静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据入口

因为规范相对宽松,也就成就了一些技术 从ZIP中读取如JAR EAR WAR,从网络中读取也就有了Applet,运行时计算生动态代理技术 java.lang.reflect.Proxy中,用了ProxyGenerator.generateProxyClass来为特定接口生成形式为,其它文件如jsp,数据库如sap

数组类

  • 类是引用类型 则按该引用类去加载
  • 不是引用类型如int[],Java虚拟机将会把数组标记为与引导类加载器关联。
  • 数组的可见性与它的组件类型的可见性一致,private/public

验证

确保字节流信息符合当前虚拟机需求,且不会危害虚拟机安全【本身相对于C/C++来说是安全的】,直接java代码到字节码已经是安全的,但是class文件也可以不由java编译产生,所以有必要检查class 如访问边界以外的数据啊这种操作。咦这样的意思是 可变边界的情况,就是由jvm来检查了吗?那比如A[]大小n,访问m,那比较这个是jvm做吗?A[3]访问x,由java直接写,但是转换出来的代码中会有检查吗?还是也丢给jvm做?再说A[x]访问x,在java中显示写x大小判断,还会被jvm检查吗?还是说有开关

说验证在整个加载系统中占了很大一部分

  • 文件格式验证 如 魔数开头,主次版本是否满足当前虚拟机,常量池是否有不被支持的常量,指向常量索引是否指向不存在的,utf8编码格式部分是否符合utf8编码 等等等等等
  • 元数据验证,是否符合java语言规范????不是jvm语言规范??,说是例如 类是否有父类,或者不符合规则的重载
  • 字节码验证(最复杂),通过数据流和控制流,分析语义是否合法,比如放入一个int,取出却按照long去除,跳转不会到方法体以外的字节码上,有增加一个StackMapTable来增加方法体基本快的描述 辅助验证
  • 符号引用验证,(在解析 阶段发生) 比如private protected 是否可以被当前类访问,引用通过字符串描述的全限定名是否能找到对应类

准备

分配内存和设置初始值,非final的都是设置为0,final的是变成constantVluae赋值,例如static int a=123;是赋值为0的,在()中的putstatic才赋值为123

解析

根据常量池符号引用替换为直接引用,相当于把名称描述换为具体的指针、偏移量什么的,只规定在具体的16个(见书)操作符号引用的字节码指令之前进行解析,

以及上面说的解析完后会有符号引用验证比如访问权限,

字段解析,如果本身有返回,否则去找父类,递归向上找

类方法解析/接口方法解析,类似原理,但会先检查class_index的位置是否对应正确,如类的应该对应类,接口的对应接口

初始化

加载的最后一步,执行构造器()方法的过程

这个是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}合并产生的,顺序根据收集顺序决定

可以可以代码7-5有点意思哈

然后保证父类的clinit在子类的clinit前执行,也就是vm执行的第一个clinit是java.lang.Object的

保证多线程下一个类的clinit被正确加锁同步,多个线程同时初始化一个类,只有一个会执行clinit,也就是可能造成阻塞

类加载器

类的全限定名->描述此类的二进制字节流 放在jvm外部实现

层次划分、OSGi、热部署、代码加密等领域

同一个Class文件同一个虚拟机加载,但只要类加载器不同,类就必定不同

样例代码是,实现了一个动态获得Class的方法(区别于默认返回类,也就是这里是让jvm用一个不同的类加载器),然后用该方法获得A的Class 为B,但是 判断 A instanceof B返回却是false

对jvm来讲只有两种不同的,一个bootstrap类加载器(属于vm),其它所有类加载器(都由java语言实现 独立于虚拟机外部)

或者分为bootstrap,extension,application三种加载器

这里作者说,可以实验自己写一个rt.jar类库已有的重名java类,它可以正常编译,但永远无法执行

以上是”双亲委派模型”给了源码,也就是找 前面描述的加载过程,也就意味着,不同的类加载器就算用同样的代码但 对应的数据不同,所以无法分别加载

该模型可被破坏 对原来兼容(1.2以前),需要回调(JNDI服务),程序动态性(代码热替换 模块热不熟 OSGi(非双亲 更复杂网状 以Bundle+类加载器整体替换) )

8 虚拟机字节码执行引擎

运行时栈帧(Stack Frame)结构, 包括 局部变量表 操作数栈 动态链接 方法返回地址 额外附加信息

局部slot,再调用其它时才有可能gc,因为 这时才有可能复用,然后局部变量不能未赋初值就用

操作数栈从描述上就是字面意思,在概念上是独立的,,在一些具体的实现优化过程中,会用重叠来减少复制传递

返回方式 ret 正常的完成出口,异常出口 自动异常或者手动athrow

然后在实现虚拟机时,可以附加一些规范外的辅助信息

方法调用

也就是说invoke[…]对应的具体位置是哪,要经过解析把描述转化为具体的序号

静态分派:样例代码 A a=new B(),对一个 实例对象 进行父类转化,再调用重载函数,重载函数选择的是父类对应的接口

这里称A为静态类型在编译时期可知,B为实际类型,在运行时才可知,依赖静态类型来执行的叫做 方法重载

然后重载 的话,对参数类型有它的一个优先顺序

动态分配(Override): A a=new B(),调用方法f,也就是a.f(),jvm是如何定位的,通过javap得出的字节码,发现 样例代码中所有的都指向(静态类型)父类的函数 都是invokevirtual,然后这个过程是 先找实际类型,再逐个往上早父类的,其中只要成功找到就执行 再返回

静态单分派与动态多分派,这个和我上面自己做的实验一致了,就是 父类没被覆盖的也存在没有dead,父类自己调用就是自己的,从字节码上看父类子类调用基类的函数时,都是invokevirtual指向基类

补充 方法分派的几个例子

所以java目前是 静态多分派,比如父类子类invokevirtual指向的同一个,动态单分派,具体指向动态的

虚拟机动态分派实现:虚拟方法表vtable,以及虚拟接口方法表itable,emmmm就是每个类有个方法表中间存了方法入口地址233333

动态类型语言支持

静态类型 statically: 如果在编译时拒绝ill behaved程序,则是statically typed;

动态类型dynamiclly: 如果在运行时拒绝ill behaviors, 则是dynamiclly typed。

  • Reflection 是在模拟 Java 代码层次的方法调用,而 MethodHandle 是在模拟字节码层次的方法调用
  • Reflection 包含信息更多更全重量级,MethodHandle轻量级,包含相关方法信息
  • Reflection for JAVA,MethodHandle for jvm

invokedynamic相对于MethodHandle来说一个是 字节码,一个是JavaAPI。。。然后invokedynamic面向的不是java是面向的其它语言生成的class

invokedynamic分派逻辑不是由虚拟机决定,而是程序员决定

解释执行引擎

大多数语言,词法分析->语法分析->抽象与法树(AST)

例如1+1基于栈是(可移植性)

iconst_1
iconst_1
iadd
istore_0

基于寄存器(性能)

mov 1 eax
add 1 eax

虚拟机可以把它转换

9 类加载及执行子系统的案例与实战

案例1 Tomcat等服务应用程序 正统的类加载器架构

因为 重用类库,安全性,支持JSP等的HotSwap,,所以都有隔离单独的类加载器。

下面是Tomcat为例,

  • /common目录中:类库可被Tomcat和所有的Web应用程序共同使用。
  • /server目录中:类库可被Tomcat使用,所有的Web应用程序都不可见。
  • /shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
  • /WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

Tomcat 自定义了多个类加载器,按照双亲委派模型实现

Spring相当于在上层,用户的在下层,模型特点是 职能向上加载不能向下。所以spring是通过 线程上下文类加载器来实现 访问并不在其加载范围内的用户程序

学习JEE规范,去看JBoss源码;学习类加载器,就去看OSGi(Open Service Gateway Initiative)源码

OSGI从层次依赖变成平级依赖,可以import 或者 export,哇 这不是 typescript那些吗,难道typescript灵感来源于此

OSGI可以热插拔,当一部分更新时,可以只停用,重新安装然后启用程序中一部分

运行时网状结构

但循环依赖可能出现死锁,

逆向移植(把代码 放到更老的版本上去执行)Retrotranslator,jvm层面的做不到,但是可以 把java->jvm层面的做不少

远程执行功能

  • 不依赖JDK版本1.4-1.7都能执行
  • 不改变原有服务端程序部署,不依任何第三方库
  • 不侵入原有程序
  • 临时代码需要直接支持JAVA
  • 临时代码具备一定自由度,不需要依赖特定的库或接口
  • 执行结果 包括输出 异常 能返回到客户端

惊了 说只要5个类+250行代码,1个半小时,,,对不起 我不会JAVA

问题

  1. 如何编译提交到服务器的java代码
  2. 如何执行编译之后的java代码
  3. 如何收集java代码的执行结果

问题1 方案A 传代码,使用tools.jar 来编译,或者javac,缺点是服务端需要tools。 方案B 在客户端编译,传字节码到服务端

问题2 让类加载器加载这个类生成Class对象,然后又反射调用某个方法就可以了,执行完后应当被卸载回收掉

问题3 直接在执行的类中把对System.out的引用替换为准备的PrintSteam,,

实现见 Remote-classload 仓库

  1. 本地运行劫持err和out输出的HackSystem
  2. 用户class->调用JavaClassExecuter.execute(byte[])
  3. ClassModifier+ByteUtils 两个类 把传入的class中 所有调用java/lang/System的换成你所运行的HackSystem
  4. 运行HotSwapClassLoader(),加载修改过后的class
  5. 运行加载了的class
  6. 其中有错误或者输出,则由HackSystem输出

10 早期(编译器)优化

  • 前端编译器(java->class): ECJ,Java
  • JIT(Just In Time Compiler)字节码->机器码: HotSpot VM的C1 C2编译器
  • AOT(Ahead Of Time Compiler): GNU Compiler for the Java(GCJ)、Excelsior JET

javac 几乎没有优化,对Java语言编码过程的优化措施来改善程序员编码风格和提高编码效率

Javac 使用Java编写和少量C语言

源码位置javac:

sudo apt install openjdk-8-source
ls /usr/lib/jvm/java-8-openjdk-amd64/src.zip
解压它 找 sun/tools/javac中

eclipse 默认禁止一些代码访问规则,所以要调试javac的话需要 把这个开关关闭

javac 的3个过程

  • 1解析与填充符号表过程
  • 2插入式注解处理器的注解处理过程
  • 3分析与字节码生成过程

其中1、2 这两步可以循环 ->1->2-1->2->1->2->3->

javac动作入口com.sun.tools.javac.main.JavaCompiler,上面3个步骤集中在类的compile()和compile2()方法中

  1. 词法语法分析 parseFiles()完成

词法分析com.sun.tools.javac.parser.Scanner类实现 源代码字节流转变为Token集合

语法分析 com.sun.tools.javac.parser.Parser产生出com.sun.tools.javac.tree.JCTree类 是根据Token序列构造抽象语法树的过程AST,每个节点表示程序代码中的一个语法结构,例如Eclipse 有 AST View插件可以看

  1. 填充符号表 enterTrees()方法

符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,也就是Key-Value模式(有的是有序 有的是树状 有的是哈希 栈结构等)

在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码

填充过程com.sun.tools.javac.comp.Enter类实现,过程出口是一个待处理表(To Do List)包含了每一个编译单元的抽象语法树的定级节点, 以及 package-info.java(如果存在的话)的顶级节点。

  1. 注解处理器(Annotation) 规范JSR-269

可以看做插件,可以读取修改 抽象语法树 元素,这个过程执行后 编译器回到解析及符号填充表重新处理,直到所有插入式注解都结束

插入式注解处理器 初始化过程initProcessAnnotations(),执行过程processAnnotations()判断是否还有注解如果有com.sun.tools.javac.processing.JavacProcessingEnvironment 类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。

  1. 语义分析与字节码生成

语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。[这里举例是不同类型做加法]

  • 标注检查 attribute() 比如类型 变量赋值类型的匹配,1+2折叠成3,具体的类是com.sun.tools.javac.comp.Attrcom.sun.tools.javac.comp.Check
  • 数据及控制流分析 flow() 上下文,比如是否初始化局部变量,方法每条路径是否都有返回值,异常有没有被正确处理,具体由com.sun.tools.javac.comp.Flow处理

变量的不变性仅仅由编译器在编译期间保障,这里的例子是是否有final最终的class文件是没有区别的

  1. 解释语法糖

java低糖语法 如 泛型、变长参数、自动装箱/拆箱 等。这些语法被编译器转换为 jvm支持的更基础的指令。解释过程由 desugar() 方法触发,在 com.sun.tools.javac.comp.TransTypes 类和com.sun.tools.javac.comp.Lower 类中完成。

  1. 字节码生成

com.sun.tools.javac.jvm.Gen类来完成,

这个阶段把 init和clinit添加到语法树中,也就是生成构造器工作,以及一些代码替换工作如把字符串的加操作替换为 StringBuffer 或 StringBuilder的 append() 操作等

关于语法糖的背后

泛型与类型擦除

也就是由C++的模板Template思想起,【之前没看过java的时候一个面试我C++的问我C++中的泛型我一脸懵逼

老版本是强行转化,所以能不能转的风险交给程序员,

List<int>List<String>是两个不同类型,在运行期间有不同的虚方法表和类型数据,称为类型膨胀

然后java中只有java中存在泛型,class文件中 已经都替换为原生类型

【举例了两个 只接受List参数的函数 来进行编译和反编译,第二个例子下采用了不同类型返回值】这里说因为 采用类型擦除,只靠List内的类型不同已经不能区分,第二个例子 只有Sun JDK 1.6能编译,并且违反了返回值不参与的原则?

测了一下报错error: name clash: f(List<String>) and f(List<Integer>) have the same erasure

真实原因是不同返回值以后, class文件下,擦除了类型的函数具体描述才能不同,才能共存于同一个class中。当然也只有Sun JDK 1.6支持

下面一段没看懂Signature部分是啥 签名?感觉大自意思是 有的程序需要识别你擦除的部分,所以还需要额外的影响和改动。以及,从 Signature 属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的 Code 属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。

1
由于 Java 泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都有可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP 组织对虚拟机规范作出了相应的修改,引入了诸如 Signature、LocalVariableTable 等新的属性用于解决伴随而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别 49.0 以上版本的 Class 文件的虚拟机都要能正确地识别 Signature 参数。

自动装箱、拆箱与遍历循环(Foreach)

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4); // 自动装箱
// 如果在 JDK 1.8 中,还有另外一颗语法糖
// 能让上面这句代码进一步简写成 List<Integer> list = [1, 2, 3, 4];
int sum = 0;
for (int i : list) { // 循环遍历+自动拆箱 变成迭代器每一个元素取出 并 转换为int类型
sum += i;
}
System.out.println(sum);
}

上面包含了泛型、自动装箱、自动拆箱、遍历循环与变长参数 5 种语法糖

错误用法: 完了要是面试问下面这个输出 我就凉凉: 包装类的“==” 运算在不遇到算术运算的情况下不会自动拆箱,以及它们 equals() 方法不处理数据转型的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);// 比值
System.out.println(e == f);// 超过[-128,127]比地址
System.out.println(c == (a + b));// 被自动装箱 转换==
System.out.println(c.equals(a + b));// 自动装箱equals 比 值&类型
System.out.println(g == (a + b)); // 自动装箱==
System.out.println(g.equals(a + b)); // 自动装箱equals 比 值&类型
}

emmmm感觉java需要手工加类型转换[或者编译检测有参数选项可以打开?

条件编译 解除语法糖阶段(com.sun.tools.javac.comp.Lower类中完成)

如C++中 #ifdef,java可以用if+常量的方式实现条件编译,2333,66666,,, 其它判断没有这个能力

除了上面的一些语法糖还有 内部类、枚举类、断言语句、对枚举和字符串(在 JDK 1.7 中支持)的 switch 支持、try 语句中定义和关闭资源(在 JDK 1.7 中支持)等

插入式注解处理器 实验

使用 JSR-296 中定义的插入式注解处理器 API 来对 JDK 编译子系统的行为产生一些影响。

javac主要在于看 程序写得对不对,而少的去看程序写得好不好。有辅助工具 CheckStyle、FindBug、Klocwork等,基于java源码或字节码校验

本实验是利用 注解处理器API来编写一款拥有自己编码风格校验工具 NameCheckProcessor

功能:仅仅对Java程序明明进行检查

  • 类(或接口):符合驼式命名法,首字母大写。
  • 方法:符合驼式命名法,首字母小写。

字段:

  • 类或实例变量:符合驼式命名法、首字母小写。
  • 常量:要求全部由大写字母或下划线构成,并且第一个字符不能是下划线。

实现

我们实现注解处理器的代码需要继承抽象类 javax.annotation.processing.AbstractProcessor

这个类只有一个必须覆盖的abstract方法process()

这个函数在javac执行注解处理器代码时要调用的过程,

https://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/AbstractProcessor.html

上面这个链接的example

1
2
3
4
abstract boolean

process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
Processes a set of annotation types on type elements originating from the prior round and returns whether or not these annotations are claimed by this processor.

第一个参数 “annotations” 中获取到此注解处理器所要处理的注解集合,从第二个参数 “roundEnv” 中访问到当前这个 Round 中的语法树节点,每个语法树节点在这里表示为一个 Element。

在 JDK 1.6 新增的 javax.lang.model 包中定义了 16 类 Element,包括了 Java 代码中最常用的元素,如:

  • 包(PACKAGE)
  • 枚举(Enum)
  • 类(CLASS)
  • 注解(ANNOTATION_TYPE)
  • 接口(INTERFACE)
  • 枚举值(ENUM_CONSTANT)
  • 字段(FIELD)
  • 参数(PARAMETER)
  • 本地变量(LOCAL_VARIABLE)
  • 异常(EXCEPTION_PARAMETER)
  • 方法(METHOD)
  • 构造函数(CONSTRUCTOR)
  • 静态语句块(STATIC_INIT,即 static{} 块)
  • 实例语句块(INSTANCE_INIT,即 {} 块)
  • 参数化类型(TYPE_PARAMERTER,既泛型尖括号内的类型)
  • 未定义的其他语法树节点(OTHER)

除了 process() 方法的传入参数之外,还有一个很常用的实例变量 processingEnv,它是AbstractProcessor 中的一个protected 变量,在注解处理器初始化的时候(init() 方法执行的时候)创建,继承了 AbstractProcessor 的注解处理器代码可以直接访问到它。它代表了注解处理器框架提供的一个上下文环境,要创建新的代码、向编译器输出信息、获取其他工具类等都需要用到这个实例变量


返回值 true/false表示是否对代码进行更改,如果未更改,javac也就不会再处理?

所有的运行都是单例 这里要实现的 功能仅是检查,所以始终返回false

注解处理器NameCheckProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 可以用 "*" 表示支持所有 Annotations  
@SupportedAnnotationTypes("*")
// 只支持 JDK 1.6 的 Java 代码
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class NameCheckProcessor extends AbstractProcessor{

private NameChecker nameChecker;

/**
* 初始化名称检查插件
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
nameChecker = new NameChecker(processingEnv);
}

/**
* 对输入的语法树的各个节点进行名称检查
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
for (Element element : roundEnv.getRootElements()) {// 获取当前Round的每一个 RootElement
nameChecker.checkNames(element); // 检查
}
}
return false;
}
}

具体检查代码太长就不贴全了,贴几个主要的函数

1
2
3
public NameChecker(ProcessingEnvironment processingEnv) {  
this.messager = processingEnv.getMessager(); // 之后可以调用messager.printMessage()来输出 信息比如 不符合规则的具体内容和具体规则
}

检查部分继承了ElementScanner6<Void, Void>

之后就是复写这个类的对应函数,对于访问element的部分根据文档 调用函数得到element的名字 和element的类型,对于名字判断就是if case的事情

它通过一个继承于 javax.lang.model.util.ElementScanner6 的 NameCheckScanner 类,以 Visitor 模式来完成对语法树的遍历,分别执行 visitType()、visitVariable() 和 visitExecutable() 方法来访问类、字段和方法,这 3 个 visit 方法对各自的命名规则做相应的检查,checkCamelCase() 与 checkAllCaps() 方法则用于实现驼式命名法和全大写命名规则的检查。

这一章 我有复制较多的原书上的话,我认为它们已经概括总结得不太能再简略了,就直接用了,也把不是很必要的代码省去

11 晚期(运行期)优化

解释器+编译器(包含C1 client 编译器 C2 server编译器)并存

C1 简单可靠优化 有必要时加入性能监控逻辑

C2 耗时较长的优化,不可靠的激进优化

触发从解释器到编译器: 采样热点(一定周期发现某个方法经常出现在栈上),计数热点(阈值+计数器判定)

触发编译器到解释器:如动态的类

HotSpot 采用的是计数器热点探测, 包含两类计数器 (方法调用计数器,回边计数器) 都有各自的阈值

除了单纯的计数 还有一个根据时间的热度半衰期 23333 学习于自然之中

回边计数器是为了统计循环体代码执行的次数

client下 回边计数阈值 = 调用计数阈值xOSR比率 (On Stack Replace Rercentage) / 100

server下 回边计数阈值 = 调用计数阈值xOSR比率 (On Stack Replace Rercentage)xjiesh / 100

[嗯!!! 终于这个代码是hpp后缀的了 吼呀

只要编译还没完成 都是按照解释器继续执行 而不是等待

但可以 加参数禁用后台编译 则是 等待形编译

第一个阶段 一个平台独立的前端将字节码构造成一种高级中间代码表示(High-Level Intermediate Representation,HIR

在第二个阶段,一个平台相关的后端从 HIR 中产生低级中间代码表示(Low-Level Intermediate Representation,LIR),而在此之前会在 HIR 上完成另外一些优化,如空值检查消除、范围检查消除等,以便让 HIR 达到更高效的代码表示形式。

最后阶段是在平台相关的后端使用线性扫描算法(Linear Scan Register Allocation)在 LIR 上分配寄存器,并在 LIR 上做窥孔(Peephole)优化,然后产生机器代码.几乎能达到 GNU C++ 编译器使用 -O2 参数时的优化强度,它会执行所有经典的优化动作,如无用代码消除(Dead Code Elimination)、循环展开(Loop Unrolling)、循环表达式外提(Loop Expression Hoisting)、消除公共子表达式(Common Subexpression Elimination)、常量传播(Constant Propagation)、基本块重排序(Basic Block Reordering)等

还会实施一些与 Java 语言特性密切相关的优化技术,如范围检查消除(Range Check Elimination)、空值检查消除(Null Check Elimination,不过并非所有的控制检查消除都是依赖编译器优化的,有一些是在代码运行过程中自动优化了)等。另外,还可能根据解释器或 Client Compiler 提供的性能监控信息,进行一些不稳定的激进优化,如守护内联(Guarded Inlining)、分支频率预测(Branch Frequency Prediction)等


-XX: +PrintCompilation要求虚拟机在即时编译时将被编译成本地代码的方法名称打印出来

带有 % 的输出是由回边计数器触发的OSR

-XX: +PrintInlinint 要求虚拟机输出方法内联信息

优化技术可以看Sun官方的Wiki,也可以搜索 即时编译器优化技术一览 ,向这么多方法低头

示例:

  1. 类的get方法,如果用内联优化,就是把get的方法变成点参数,是可以减少栈帧的开销,又能为其它优化打好基础

  2. 访问同一个类的参数,可以消除冗余的访问

  3. 把没有意义的进行复写传播 以及无用的代码消除 如 y=x;z=y;k=z+x直接就变成k=x<<1

嗯。。。以前编译原理课都有讲过上面列举的

  • 语言无关的经典优化技术之一:公共子表达式消除。表达式计算过 在没有变化的情况下 再次计算
  • 语言相关的经典优化技术之一:数组范围检查消除。消除绝对没有越界的数组访问 和安全性相呼应【如果不确定是否越界还是要做的】
  • 最重要的优化技术之一:方法内联。
  • 最前沿的优化技术之一:逃逸分析。

除了如数组边界检查优化这种尽可能把运行期检查提到编译期完成的思路之外,另外还有一种避免思路——隐式异常处理,Java 中空指针检查和算术运算中除数为零的检查都采用了这种思路

1
2
3
4
5
if (foo != null) {
return foo.value;
else {
throw new NullPointException();
}

在使用隐式异常优化之后,虚拟机会把上面伪代码所表示的访问过程变为如下伪代码。

1
2
3
4
5
try {
return foo.value;
} catch (segment_fault) {
uncommon_trap();
}

虚拟机会注册一个 Segment Fault 信号的异常处理器(伪代码中的 uncommon_trap()),这样当 foo 不为空的时候,对 value 的访问是不会额外消耗一次对 foo 判空的开销的。代价就是当 foo 真的为空时,必须转入到异常处理器中恢复并抛出 NullPointException 异常,这个过程必须从用户态转到内核态中处理,结束后再回到用户态,速度远比一次判空检查慢。当 foo 极少为空的时候,隐式异常优化是值得的,但假如 foo 经常为空的话,这样的优化反而会让程序更慢,还好 HotSpot 虚拟机足够 “聪明”,它会根据运行期收集到的 Profile 信息自动选择最优方案。


方法内联

为了解决虚方法的内联问题,Java 虚拟机设计团队想了很多办法,首先是引入了一种名为 “类型继承关系分析”(Class Hierarchy Analysis,CHA)的技术,这是一种基于整个应用程序的类型分析技术,它用于确定在目前已加载的类中,某个接口是否有多于一种的实现,某个类是否存在子类、子类是否为抽象类等信息。

编译器在进行内联时,如果是非虚方法,那么直接进行内联就可以了,这时候的内联是有稳定前提保障的。如果遇到虚方法,则会向 CHA 查询此方法在当前程序下是否有多个目标版本可供选择,如果查询结果只有一个版本,那也可以进行内联,不过这种内联就属于激进优化,需要预留一个 “逃生门”(Guard 条件不成立时的 Slow Path),称为守护内联(Guarded Inlining)


逃逸分析(Escape Analysis)

它与类型继承关系分析一样,并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术

证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化

  • 栈上分配(Stack Allocation):Java 虚拟机中,在 Java 堆上分配创建对象的内存空间几乎是 Java 程序员都清楚的常识了,Java 堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但回收动作无论是筛选可回收对象,还是回收和整理内存都需要耗费时间。如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将会是一个很不错的注意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。
  • 同步消除(Synchronized Elimination):线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争,对这个变量实施的同步措施也就可以消除掉。
  • 标量替换(Scalar Replacement):标量(Scalar)是指一个数据已经无法再分解成更小的数据来表示了,Java 虚拟机中的原始数据类型(int、long 等数值类型以及 reference 类型等)都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它就称作聚合量(Aggregate),Java 中的对象就是最典型的聚合量。如果把一个 Java 对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配至物理机器的告诉寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。

关于逃逸分析的论文在 1999 年就已经发表,但直到 Sun JDK 1.6 才实现了逃逸分析,而且直到现在这项优化尚未足够成熟,仍有很大的改进余地。不成熟的原因主要是不能保证逃逸分析的性能收益必定高于它的消耗。

目前发展并不够成熟


Java与C++编译器对比

  1. 即时编译器运行占用的是用户程序的运行时间, 因此即时编译器 不敢大量引入优化技术
  2. JAVA是动态的类型安全,因此有很多安全方面的检查开销 是不会彻底的被编译器优化掉的
  3. 虽然java没有virtual但虚方法使用频率远大于C++,这方面的优化难度也更大
  4. 动态语言,编译器难以看清全貌,职能运行时撤销或者重新进行一些优化
  5. C++有很多在栈上,而java很多在堆上,垃圾回收效率

【可以 ,,,基本是java的劣势,但是 并不是说不如,而是说有 安全 动态性等所作出的 代价回报

有java更容易分析的 别名分析 Java 的类型安全保证了在类似如下代码中,只要 ClassA 和 ClassB 没有继承关系,那对象 objA 和 objB 就绝不可能是同一个对象,即不会是同一块内存两个不同别名。

void foo(ClassA objA, ClassB objB) {
objA.x = 123;
objB.y = 456;
// 只要 objB.y 不是 objA.x 的别名,下面就可以保证输出为 123
print(objA.x);
}

但是C++有union之类的???

另外的话 JAVA能够在 运行期间有调用频率预测 分支频率预测 等动态的运行红利

个人感觉就是 有大量相似、相同的优化技术,但因为优化的源和目标结果并不相同会有一些差异。差异可以概括为用性能换得了更多的可控和安全性(比如 安全性 是相对于同样精力编码来说的?因为C++要强行写应该也能做出安全保障吧?),而优化技术可以看做基本都是那么些方法

12 Java内存模型与线程

震惊 希望计算机能同时做其他事的原因竟然是 io等其它速度太慢,把这些原本的等待时间 压榨出来

java 带有的并发逻辑 更加容易让程序员关注业务逻辑,而不是协调

现代 加上cache,但面临一致性问题,为了解决一致性缓存问题,协议有MSI MESI MOSI Synapse Firefly Dragon Protocol


内存模型JMM(java memory model),用于让在不同的硬件和不同的操作系统上达到效果一致[相对于C++使用物理硬件来说]

需要严谨 & 宽松

主内存与工作内存 之间的交互协议

  • lock 作用于主内存的变量,它把一个变量标识为一条线程独占的状态
  • unlock 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中
  • load 作用于工作内存变量,把read 操作从主内存中得到的变量值放入工作内存的变量副本中
  • use 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,虚拟机需要使用变量的值得字节码指令时,将会执行这个操作
  • assign 作用于工作内存变量 把执行引擎接收到的的值付给工作内存的变量,
  • store 作用工作内存变量,把值传送到主内存方便以后write操作使用
  • write 写入 作用于主内存的变量,它把store 操作从工作内存中得到的变量的值防御主内存中

流线可以看作,【似乎下面的意思这些都原子 不需要lock,只有程序员想额外保证原子的再lock?

read(主内存到线程内存) -> read(从线程内存到变量副本中) -> use(使用)

assign(写) -> store(变量副本到从线程内存) -> write(写入主内存)

  1. 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  2. 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  3. 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  4. 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
  5. 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  6. 如果对一个变量执行了lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前, 需要重新执行load或assign操作初始化变量的值。
  7. 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其它线程锁定住的变量。
  8. 对一个变量执行unlock操作之前,必须把此变量同步回主内存中(执行store、write操作)。

以及Volatile的规定,当一个变量定义为volatile之后,它将具备两种特性:

  1. 保证此变量对所有线程的可见性;普通变量(没被volatile修饰)做不到这一点,普通变量的值在线程间传递均需要通过主内存来完成。
  2. volatile变量不是线程安全的;volatile变量在各个线程的工作内存中不存在一致性问题(在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。

即使编译出来只有一条字节码指令,也并不意味执行这条指令就是一个原子操作。 所以需要同步保护(synchronized或 java.util.concurrent)

这里是说用 lock add $0x0 (%esp)的技巧 来制作重排序屏障 理论上是比锁快

对于long和double型变量的特殊规则

要求上面lock unlock等8个操作都是原子性, 但允许没有被volatile的64位类型读写划分为两次32位操作,也就是64位 可以不保证load store read write的原子性【 所以可能读到半个变量,虽然标准中没有原子规定,但是建议的虚拟机实现最好是原子的实现

  • 原子性: 内存模型直接保护 就是除了lock和unlock,如果用户需要更多原子性保护可以用lock和unlock,对于更高层次,虚拟机提供了monitorenter和monitorexit来隐式使用两个操作,再向上说就是java中 的synchronized
  • 可见性:volatile的特殊规则保证了新值能够立即 同步到主内存
  • 有序性: 线程内是有序的 volatile(包含禁止重排序的语义) synchronized(包含一个变量同一时刻只允许一条线程对其lock)

顺序性

  • 程序次序规则:根据控制流来说 控制流前面的代码先执行
  • 管程锁定规则 一个unlock先于同一个锁的lock
  • volatile 写先于(后面的)读
  • 线程启动规则,Thread对象的start()方法先行发生于次线程的每一个动作
  • 线程中断规则: 线程中断调用 先于 中断检测
  • 对象终结规则, 初始化先于finalize()
  • 传递性: A先于B B先于C 则 A先于C

在满足上面的无同步手段保障时 可以随意重排序,线程不安全的例子:

1
2
3
4
5
6
7
8
private int value = 0;
public void setValue(int value){
this.value = value;
}

public int getValue(){
return value;
}

上面规则都不适用,但是缺无法确定不同线程同时执行时这里的返回结果,所以它是线程不安全的

修复问题 解决方案

  1. getter/setter都定义为synchronized方法 可以套用管程规则
  2. 把值定义为volatile变量

java提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start()且未结束的java.lang.Thread类的实例就代表了一个线程

实现方法主要有3种:

  • 内核线程实现(Kernel-Level Thread) 直接让内核调度器调度,层级关系CPU-线程调度器-KLT-LWP(Light Weight Process)-程序使用
  • 用户线程实现 (User Thread), 完全建立在用户控件的线程库上,系统内核不能感知存在的,可以降低 用户态和内核态切换的消耗,java和ruby曾经用过最终放弃了?
  • 用户线程实现 加 轻量级进程混合实现,solaris HP-UX提供了相应的模型实现,和内核线程实现比起来是在LWP和程序使用之间 用UT

线程调度 协同或者抢占式

java设置了10个级别的线程优先级,windows 有7种,solaris有2^32种。。。。 windows还有一个 优先级推进器6666

5个状态

  • 新建
  • 运行
  • 等待(无限等待 时间等待)
  • 阻塞 相对于等来说就是有一个明确的阻塞目标???
  • 结束

13 线程安全与锁优化

你看这个本书都说 google都找不到“线程安全”的标准定义了 :-) 面试官还一天问个不停

java中的线程安全

  1. 不可变 -一定安全 只要被正确构建 final 以及例如java.lang.String 枚举类型等
  2. 绝对线程安全,不管运行时环境如何,调用者都不需要任何额外的同步措施,,,,这里说理论上没有 绝对的库还能向上用,因为你上层的实现可能是对一个线程安全的进行分步操作,,,,
  3. 相对线程安全,,通常意义的线程安全
  4. 线程兼容,,,本身不是线程安全,但可以通过 同步手段让它们达到线程安全
  5. 线程对立,,,无论如何都无法达到

线程安全的实现

  1. 互斥同步,临界区 互斥量 信号量 都是主要手段,。。。。互斥是因 同步是果,互斥是方法 同步是目的

synchronized 同步 对同一个线程来说是可以重入的

也可以使用java.util.concurrent 包中的重入锁(ReentrantLock),相对于synchronized多了 三个功能,等待可中断,公平锁,锁绑定多个condition,,,然后性能说 单核多线程,线程数量上去后 ReentrantLock表现更好jdk1.5,之后版本优化得差不多,所以能直接sync实现也就不用麻烦

  1. 非阻塞同步 emmm这就是类似乐观锁 而上面就是悲观锁

需要硬件支持

  • 测试并设置
  • 获取并增加
  • 交换
  • 比较并交换 CAS
  • 加载链接、条件存储
  1. 无同步方案

只要保证可重入,没有因果关系,也可以实现

锁优化

高效并发是jdk1.5到1.6的一个重要改进,如

  • 自旋锁与适应性自旋,相当于说 如果锁的交替很快,那么自旋锁实际就是通过帮你多等一会,来节省内核态切换的开销,默认等待次数10次,然后自旋锁再自适应次数,就能更准确的预测等待次数。
  • 锁清除,清除无意义的锁 也就是前面逃逸分析里提到过的
  • 锁粗化 原则上是同步块越小越好,但连续的拿锁放锁有较大不必要的性能消耗
  • 轻量级锁 使用CAS 相对于操作系统的互斥量的锁,并不是代替操作系统互斥量锁,,,emm相当于先简单的读写尝试 成功则运行,不然膨胀为重量级锁 允许多个线程获得 但不允许竞争
  • 偏向锁(Biased Locking) 和轻量级锁类似,但它向上升级就是轻量级锁 只允许一个线程获得

总结

感觉看jvm最好先学C++一遍再看一遍操作系统,反而java不怎么需要看,感觉作者是站在你已经会C++的程度上来写的这本书

本Markdown文档依赖和感谢

《深入理解Java虚拟机》盗版pdf XD

Ubuntu 18.04 bionic && java-8-openjdk-amd64 && 各种工具了

董哥的指路

网上各个对该书的图文完全复制的博客,[以及chrome的去广告插件和自定义css插件

google+stackoverflow对相关问题的回答

github上搜到的书中所述的相关代码仓库

在线java编辑编译器https://www.tutorialspoint.com/compile_java_online.php

[排序根据贡献从前到后]

安装配置

Prepare

  1. install brainfuck by downloading the package from ubuntu packages

  2. install npm & LiveScript

1
2
sudo apt install npm
sudo npm install -g LiveScript

I don’t know what happened, livescript which is installed by apt is broken and the error message shows as below or this issue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
##################################
## 93: LiveScript -> LLVM asm ##
##################################

lsc QR.ls > QR.ll
module.js:442
throw err;
^

Error: Cannot find module 'prelude-ls'
at Function.Module._resolveFilename (module.js:440:15)
at Function.Module._load (module.js:388:25)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/usr/lib/nodejs/livescript/lib/ast.js:3:8)
at Module._compile (module.js:541:32)
at Object.Module._extensions..js (module.js:550:10)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)

Install prelude-ls haven’t fix it, however it is workable by using npm to install LiveScript to cover it.

Preview output

You can check the output branch which is generated on my computer or the spoiler branch which is povided by the original author of the code. I haven’t diff them

ref

quine

I found that I was no longer willing to write the crawler program, more often use jquery output page link, and then wget to download, this will be faster.

关于docker的介绍以及初级使用 在各个网上都能搜到了

记录一下个人的使用了两天后的感受

  1. 首先可以去看看知乎的评价 以及知乎上说的关于docker的使用场景

  2. 目前还是没有感觉出和server 虚拟机的差别, 很明显的就是 它能直接通过内部命令去搜和下载以及安装运行,而虚拟机需要去找找找

  3. 因为本身我的host也是linux而且还是基于ubuntu的mint,所以我看到很多应用 感觉都直接可以安装,从这一点上说对个人用户没有看出特别的优点

  4. 目前所想到的优点,以及搜索到的一些repo:如果本身使用的不是debian系列的linux 甚至是windows,那么有一个docker能快速部署和支持想想也是很棒的,当然现在windows 也推出了ubuntu on windows

  5. 个人每次换新电脑的时候 还是要配本地环境,感觉优势不明显,但比如一些常用的需要配置的仓库,别人已经搭好了就能直接pull

  6. 还没学会如何精简的使用docker+github 来作为个人性质的使用 _(:з」∠)_或者这种姿势本身就是不正确的?

  7. 以及 它和虚拟机相比 可以说把 整个系统当做一个仓库来管理,因此 可以更快”建立”虚拟机 用来运行一些需要隔离的程序

架构

architecture

components

docker command

docker state

https://docs.docker.com/engine/faq/

常用

docker pull

docker build

docker push

docker run

run

--rm 运行后删除自己,可以把数据类映射到宿主机上,运行的container只是当作个程序

--env 运行环境参数

但凡会输入--help命令就能看到-d-d, --detach Run container in background and print container ID 还有一堆人非要说是daemon

--link <container name or id>:<alias name> # https://www.jianshu.com/p/21d66ca6115e

权限

方案1 sudo启动

方案2 创建docker用户组

1
2
3
4
sudo groupadd docker     #添加docker用户组
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker #更新用户组
docker ps #测试docker命令是否可以使用sudo正常使用

运行

1
2
3
4
5
6
7
8
docker run --name 容器名 -i -t -p 主机端口:容器端口 -d -v 主机目录:容器目录:ro 镜像TD或镜像名:TAG

# --name 指定容器名,可自定义,不指定自动命名
# -i 以交互模式运行容器
# -t 分配一个伪终端,即命令行,通常组合来使用
# -p 指定映射端口,将主机端口映射到容器内的端口
# -d 后台运行容器
# -v 指定挂载主机目录到容器目录,默认为rw读写模式,ro表示只读

映射:冒号左侧宿主docker提供,右侧docker内部

创建

流程

docker hub 仓库,拉取到本地images

images 生成可运行的container

container 执行具体任务

docker image inspect <image name> 看配置

多个docker 之间配置 一次使用

docker-compose.yml文件 + 相关配置文件

1
2
3
4
5
6
# 启动所有服务
docker-compose up
# 关闭所有服务
docker-compose stop
# 删除所有
docker-compose rm

Dockerfile 创建image Demo

index.js

1
2
3
var os = require("os");
var hostname = os.hostname();
console.log("hello from " + hostname);

Dockerfile

1
2
3
4
5
6
FROM alpine
RUN apk update && apk add nodejs
COPY . /app
WORKDIR /app
EXPOSE 3000
CMD ["node","index.js"]

docker image build -t hello:v0.1 .

相关命令

  • 基础镜像(父镜像)信息指令 FROM。
  • 维护者信息指令 MAINTAINER。
  • 镜像操作指令 RUN 、EVN 、ADD、 WORKDIR、EXPOSE 等。
    COPY 从宿主到内部
  • 容器启动指令 CMD 、ENTRYPOINT 和 USER 等。
    CMD 内部执行的命令 推荐上面数组格式
    ENTRYPOINT 可以让你的容器表现得像一个可执行程序一样。

使用多个container (swarm mode)

Docker Compose and Docker Swarm Mode

Compose用于控制单个系统上的多个容器。 就像我们用来构建映像的Dockerfile一样,有一个文本文件描述了应用程序:要使用的映像,多少实例,网络连接等。但是Compose仅在单个系统上运行,因此虽然有用 ,我们将跳过Compose1并直接进入Docker Swarm Mode。

Swarm Mode 告诉Docker您将运行许多Docker引擎,并且您希望协调所有引擎。 Swarm模式结合了以下功能:不仅可以定义应用程序体系结构(例如Compose),而且可以定义和维护高可用性级别,扩展,负载平衡等。 通过所有这些功能,Swarm模式在生产环境中的使用频率要比其简单的表亲Compose更高。

https://training.play-with-docker.com/ops-s1-swarm-intro/

常用

这些并不会随着container消失而清理,可能需要手动rm,

docker network ls 管理网络配置

docker volume ls 储存

安全

对于宿主机器的权限https://training.play-with-docker.com/security-seccomp

常用 cheat sheet

功能 命令
比较container和images本身的区别 docker container diff <container_id>
基于container创建image docker container commit <container_id>
命名image的repo docker image tag <image_id> <image_repository>
基于dockerfile创建image docker image build -t <image_repository> .
导出容器 docker export <container_id> > xxx.tar
导入容器 cat xxx.tar <管道符号> docker import - <repository>:<tag>
标准输出日志查看 docker logs -f <container_id> / docker container logs <container_id>
进入正在运行的容器(不建议经常操作,如果经常改,应该映射出来) docker container exec -it <container_id> /bin/bash

发布到远端

登录

docker login

产生image

docker image build -t [username]/[repository]:[tag] .

推送

docker image push [username]/[repository]:[tag]

磁盘体积过大

比如我的一个128ssd+1t普通的电脑,分区大概是/,/home,/data, 然后这个玩意占了8g多/,忍不了

很多办法 https://github.com/moby/moby/issues/3127 , 官网是建议

/etc/docker/daemon.json

1
2
3
4
{
"data-root": "/data/docker",
"storage-driver": "overlay2"
}

记得先停再操作

ref

training play with docker

Unikernels

相关连接

1

2

3

4

虚拟化是不需要对用户程序更改提供的下层虚拟服务

这个实现在云端把它作为单目标程序的VM image

贡献:

1.提供单应用密封的程序 很适合云服务

2.评估了OCaml一种函数式语言 展示了类型安全 的好处 不会造成灾难性

3.lib和language扩展支持在OCaml上编码

基于hypervisor开发 而非硬件

§3 用Mirage的实现来描述一个unikernel的原型

牺牲向后兼容 显著 提高安全和性能

specialised, sealed, singlepurpose libOS VMs that run directly on the hypervisor

配置部署内置到编译过程中…???…

做了一些云端程序大小的优化

安全firstly by compile-time specialisation, then by pervasive type-safety in the running code + 基于hypervisor和ssl ssh等

在编译时尽可能的估计不需要的feature,从而减少image大小

多语言的支持 向后兼容 vs 效率 ,类型

单语言(类型 安全) 再用非OCaml的通过消息传递 来交流

single-address space 内存需要一次申请完 从而在隔离上基于hypervisor实现密封

运行时随机地址 保护

提供Xen VM 镜像 和linux 二进制可执行mirage

总之 我们用OCaml 我们觉得它吊

PVBoot 提供两个页申请slab & extent slab 用于支持c(用的不多 因为大多数代码是OCaml写的),extent是主要的 申请分配 虚拟地址 垃圾回收等,PVBoot还提供最基本的异步 事件驱动等

3.3 运行时语言 内存分为两个大堆(长期)和小堆(临时) ,通过对heap的分化 让垃圾回收管理所需要检测的部分变小,以及实现0拷贝I/O,基于PVBoot的domainpoll函数实现的并行,只有最外的线程main的loop需要C的外部事件其它都是OCaml的,用evaluator去唤醒轻量的线程,没有内部抢占和异步中断,试用本地key可以对一个thread进行定位从而操作。

3.4 设备驱动 基于hypervisor(Xen)的驱动 信号槽 事件通道 来支持USB PCI等,通过内联少量汇编和C结构 实现基本由纯OCaml实现。 I/O 零拷贝(grant table)自动垃圾回收(但仍然不能完全防止数据泄漏)

3.5 Type-safe I/O network(内部vchan+外部不同的lib对应不同的protocol 按照cstrcut分割) 网络DNS啊什么的更快(原理是给上层更多的底层控制?减少中间内耗?)

4 Evaluation

boot time(vs linux-pv 网络响应)Unikernels are compact enough to boot and respond to network traffic in real-time.

threading 因为少用户/内核切换唤醒时间 垃圾回收 感觉主要靠超页? Garbage collected heap management is more efficient in a single address-space environment. Thread latency can be reduced by eliminating multiple levels of scheduling.

Networking and Storage (direct I/O 更高的throughput relative) 低内存拷贝 高cpu使用 和linux direct I/O比基本差不多 感觉那个表的意思是Mirage to Linux 的throughput更小??

实现了一些功能程序 =。= 然后比性能大小,还说For example, in the last 10 years the Internet Systems Consortium has reported 40 vulnerabilities in the Bind software.7 Of these, 25% were due to memory management errors, 15% to poor handling of exceptional data states, and 10% to faulty packet parsing code, all of which would be mitigated by Mirage’s type-safety.

活跃代码数=。= 感觉整个贡献也就很棒

总结也就和最开始的贡献总结一样

type-safe????

Singularity: Rethinking the Software Stack

  1. software-isolated processes for protection of programs and system services
  2. contract-based channels for communication
  3. manifest-based programs for verification of system properties

现有的系统在一些结构上还是采取老的方法,这个Singularity项目就是要复审现有的软件的常见问题 widespread security vulnerabilities; unexpected interactions among applications; failures caused by errant extensions, plug-ins, and drivers, and a perceived lack of robustness.

1 new os ,new program language(Sing#(extension of C#)) and new software verification tools,安全的编程语言能避免许多安全问题,verification tool也能提前检测出一些,增强的系统架构,作者也说这个Singularity在它们看来是个过程和意见 不是最终的一个完善的系统。

2 提供简易但能根据简易的实现更丰富的实现

2.1 Software-Isolated Processes

利用现代编程语言的类型和内存安全来说动态减少用于隔离的代码,两个程序间不能直接的内存操作,要么信号传递要么用特殊的交换heap,这样设计的sip是自动运行的,每一个sip有它自己的数据框架,运行时系统,垃圾回收,不能运行时加代码,新建sip和host的sip是隔离开的,易于去分析,最近也有paper对这种sealed processes的好处和trade-off进行分析.基于语言类型和内存安全用软件静态+运行时分析???这么吊?不需要硬件?通过这样多个程序甚至可以使用同一个大的虚拟地址,因为每个都不会超过它自己的SIP的范围,Aiken比较了硬件和软件用来孤立的trade-off,SIP减少page table切换 TLB,这个实验的关键就是建立一个用SIP的系统并论证它更加可靠..

2.2 Contract-Based Channels

通过合约channel (In the Sing# language, the endpoints are distinguished by types C.Imp and C.Exp , respectively,), 这里给的是状态来表示 以及状态转移所要进行的操作的伪代码 总之细节也记不很清,如果以后要做相关的可以来参考,这里这样是表达了所有的操作按照一个Contrat,这样是高效并且在SIP之间的交流是可以分析的,之后结合上线性类型Singularity也支持了SIP之间进行大数据的零拷贝的channel,作者说验证器开发了很久,因为它无法预测输入的数据不足的情况,并且这个bug也存在了很久,(最后的解决办法是在可能触发的几秒钟内再进行判断???) 总之通过channel 提供了分离 易于静态分析 减少一定的动态检测 并且动态也能去检测故障

2.3 Manifest-Based Programs

用户需要执行的时候不是给一个exe一样的而是给系统一个manifest,它包含source程序的位置以及要依赖的程序的信息(感觉就和Android中的权限需要 先在manifest的样子很像),通过manifest的提供 可以静态动态的分析 依赖,以及检测是否可以支持,总之MBP很强 是MSIL的一个扩展=。= 有助于 各种检查

3. SINGULARITY KERNEL

提供最基本的 software-isolated processes, contract-based channels, and manifest-based programs的抽象,To each SIP, the kernel provides a pure execution environment with threads, memory, and access to other MBPs via channels. 主要90% 使用Sing# 类型安全的语言写的 有垃圾回收机制,最主要的unsafe的代码是是实现垃圾回收的=。= 并不懂它这种48%的计量方式是什么 以及这么具体干嘛=。=_(:з」∠)_ 这也就不继续说语言组成的细节 具体看paper,总的说它是microkernel 所有的protocol 文件系统都是在核外,

3.1 ABI

Application Binary Interface (ABI) 确保最小 安全的 孤立的 每一个SIP的计算环境,ABI通过 终端的channel进行信息交流,ABI 将 accessing primitive和process-local operations 区分开,这种区分在进过通道时作为一个参数,静态分析可以利用这个信息进行分析,……比如静态分析可以知道没有使用网络channel的就没有能力发送DDOS攻击……

不是很懂这一段 怎么设计的功能有192个 又很多 又不包括一些类似win和linux的复杂的,ABI保证任何SIP不能通过ABI函数‘直接’改变另一个SIP的状态,保证了每一个SIP自己的控制权,这样让每个SIP依赖与软件层面的孤立而不是硬件的保护,然后它们的消耗比普通的函数调用消耗更大

3.1.1 Privileged Code 由于类型安全和内存安全,SIP提供了权限指令更加宽松的 执行范围,SIP并利用这种safe in-line技术 来优化channel 交流

3.1.2 Handle Table 通过强类型控制表保证SIP的跨ABI的操作例如 mutexes和threads不会被篡改,

3.2 Memory Management

又是说以往的内存的虚拟内存每个SIP一个管理,就算是分享 也是 指向自己的虚拟地址,不会指向其它SIP的虚拟地址,因此易于垃圾回收。

3.2.1 Exchange Heap 每一个SIP之间交换的数据需要放在交换heap中(Figure 3),交换heap中的指针只会指向交换heap中,在运行时同一个heap在一个时间最多被一个SIP拥有,静态分析能确保SIP不会访问一个dangling pointer.为了能静态分析….在执行时一个SIP最多有一个指向block的指针,Singularity保证一个SIP在发送一个block数据后不再拥有修改它的能力

3.3 Threads

所有tread是内核tread ,也就是内核可调度的,性能差不多 因为不需要内核权限和protected mode

3.3.1 Linked Stacks

3.3.2 Scheduler 用一个 unblocked list 和一个preempted list来,和普通发信息一样它也是发送信息,block等待返回,Singularity允许 一个SIP中的线程切换到另一个SIP上的线程,需要394个周期

3.4 Garbage Collection

大多safe language 中都有垃圾回收,Singularity的每个SIP有它自己的垃圾回收功能,又pointer不会跨SIP等前面的限定让系统能够很好的垃圾回收。现在运行时的垃圾回收有五种 generational semi-space, generational sliding compacting, an adaptive combination of the previous two collectors, mark-sweep, and concurrent mark-sweep.现在系统用的是concurrent mark-sweep collector

3.5 Channel Implementation

0allocated ,用Sing#实现,状态转换的每个周期至少一次发送和一次读取,这样保证了不会有终端无限发送而不等待,看上去这个规则过于严格,但实践没发现有放宽的需要,预申请终端的queues并把指针指向exchange heap 很自然的能够实现0拷贝的多SIP子系统

3.6 Principals and Access Control

4. DESIGN SPACE EXPLORATION

Singularity’s principled architecture, amenability to sound static analysis, and relatively small code base make it an excellent vehicle for exploring new options. In the following subsections, we describe four explorations in Singularity:

  • compile-time reflection for generative programming,
  • support for hardware protection domains to augment SIPs,
  • hardware-agnostic heterogeneous multiprocessing,
  • typed assembly language.

4.1 Compile-Time Reflection

The Java 有运行时的反射,We have developed a new compile-time reflection (CTR) facility . Compile-time reflection (CTR) is a partial substitute for the CLR’s (common language runtime) full reflection capability. The core feature of CTR is a high-level construct in Sing#, called a transform, which allows programmers to write inspection and generation code in a pattern matching and template style. The generated code and metadata can be statically verified to ensure it is well-formed, type-safe, and not violate system safety properties. At the same time, a programmer can avoid the complexities of reflection APIs.

4.2 Hardware Protection Domains

大多的os用cpu的mmu 硬件来孤立进程通过两种机制 ——1.进程只能访问具体的一些物理地址,2.使用privilege levels保护 执行权限指令的不可信代码.

主要讲的还是 权限的分配

4.3 Heterogeneous Multiprocessing

5. CONCLUSIONS

  • how to make more dependable software systems
  • software isolated processes (SIPs),
  • contract-based channels, and
  • manifest-based programs (MBPs).

总结

读后感 首先其的设计方法 还是让我有新的获取,

依然是对type-safe 的语言 一脸懵逼 问了问ssj,他说开学给我说_(:з」∠)_向大佬低头

然后就是一点感悟,不知道其它学科 往研究方向是怎么走的,至少目前看到 系统相关的 很多东西都不是”站在巨人的肩上“ 更多的像是”听了巨人说的话“,毕竟它和实际的联系会有更紧密一些,向文中的很多驱动啊什么之类的都是巨大的工作量,当对底层有修改想法的时候,还需要去考虑对上层的支持,因此这样一个就算十分优秀的想法,想要实现,想要验证,甚至想要应用都需要大量的时间和物力去支持。

我不知道未来会怎样,不知道如果我去研究会做出多少贡献,又能有多少支持。如果梦想养不活你,就挣钱养活梦想吧。

opengl自己的全以gl开头

识别图元,识别凹多边形,通过计算相邻边的叉积 来找到大于180°的角,再以此将凹多边形分割为多个凸多边形。

将凸多边形分割为多个三角形

内/外的判定,奇偶规则 或 非零环绕规则

多边形表 顶点表(x,y,z描述) 边表(用顶点描述) 面片表(用边描述)

× 平面方程与平面方程的求解 Ax+By+Cz+D=0, <0 在面的后面 >0在面的前面

绘制六边形GL_POLYGON/GL_TRIANGLES/GL_TRIANGLES_STRIP/GL_TRIANGLES_FAN的不同效果

四边形 GL_QUAD/GL_QUAD_STRIP

顶点数组 GLint points [NUM][3] ={ {0,0,0},{0,1,0}…}

glBitmap 位图函数

总评

整理开始日期:2017-07-07

整理结束日期:[整理中]

ISBN 9789812725202

….买书看封面有中文 打开后全英文 感觉又被智商压制了

目录

  • 系统
    • 系统和复杂性
    • 复杂性的来源

总评

整理开始日期:2017-06-21

整理结束日期:2017-06-22

第1章 命题逻辑

~ 否定命题

∧ 合取命题 and

∨ 析取命题 or

→ 条件命题

P→ Q = ~P∨ Q

P↑ Q = ~(P∧ Q)

P↓ Q = ~(P∨ Q)

T 永真式 重言式 可满足公式

F 不可满足公式

不同取值让不同命题有相同结果 则 命题等价

子公式等价替换后保持等价A(A1)=A(A2) 如果A1=A2

可互推则双条件命题为永真

A*与 A处处真值表为反为A的对偶式

$A\cup B={x\in A\bigvee x\in B}$

析取 子句

合取 合取范式

凡不是永真的,命题公式都存在与之等价的主合取范式

CP规则

右箭头上加c表示推的否定

第2章 一阶谓词逻辑

A是人 PEOPLE(A)

x大于y GREATER-THAN(x,y)

∀ 任意

∃ 存在

(∀ x)P(x) = (∃ x)[P(x)]

(∃ x)P(x) = (∀ x)[P(x)]

Skolem 将合取范式的所有存在用函数与任意进行替换(∀ x)(∀ y)(∃ z)P(z) = (∀ x)(∀ y)P(f(x,y))

∃ x ∀ y → ∀ y ∃ x

第3章 集合及其运算

$A\cup B={x\in A\bigvee x\in B}$

$A\cap B={x\in A\bigwedge x\in B}$

$A\bigoplus B={x\in A - B\bigvee x\in B - A}$

2^A={ X | X \subseteq A}$

运算$\times$为集合的直径 不满足交换律 结合律

形式语言

第4章 初等数论和证明方法

n=d×k -> d | n

最大公因子gcd,最小公倍数lcm

数学归纳法

第5章 二元关系

R 关系 {(A的元素,B的元素)} 也可以画成矩阵表示

性质(自反 对称 可传递)对应的现象({(x,x)} {(x,y)(y,x)} {(x,y) (y,z) (x,z)})

关系的运算 $\cup \cap \bigoplus$

同时有 自反 对称 可传递 为等价关系

同时有 自反 反对称 传递 为偏序关系 如整除,有最小值的偏序集为良序

第6章 函数

f: X→ Y

单射、满射、双射

$\pi_1\circ \pi_2 = \pi_1(\pi_2())$

循环的积

与自然数集N等势的为可数集

集合的基数card()

阿列夫 =。=

f(x)=O(g(x))不能写成O(g(x))=f(x) 表示f(x)的上界,f(x) <=M |g(x)|

第7章 基本计数方法

排列计数,组合计数

圆排列!!

组合恒等式!!!

容斥原理

抽屉原理/鸽巢原理

第8章 生成函数和递推关系

定义 生成函数!!!!!!!!!!!!! (排列的生成函数 组合的生成函数)

递推关系及其解 差分方程

递推关系和生成函数 6666666

第9章 无向图和有向图

V为节点 E为边 度 sum(每个点度)=2倍边数

G(V,E)

定义…

图的矩阵表示

独立集 点团 极图,布尔表达式 析取范式,诱导子图??

图的着色??

第10章 基本图类和算法

树与生成树

每个连通图都含有生成树,图的任何割集至少与树有一个公共边,图的圈至少与树有一个公共边

如果有向图G的图是树,则称G为有向树

平面图(画为平面图可以让连线没有额外焦点)和对偶图

面数为f的连通平面图G(n,m) 有n-m+f=2

阶数大于2的(n,m)连通平面图 m<=3n-6,至少存在一个度不超过5的结点

Kuratowski 定理

欧拉图及其应用

哈密顿图及其应用

图的匹配与匈牙利算法

网络无环又有向权图

第11章 群和环

群 环 域 格 和 布尔代数是典型的系统

运算与代数系统

集合的二元运算 封闭的 可交换的 可结合的 可分配的 吸收率

常见的代数系统 $<Z,+>$,$<Q,+,*>$

封闭 广群

广群+可结合 半群

群 和 子群

群=有逆元的含幺半群

群的运算满足交换律则为交换群/Abel群 充要条件 $a,b \in G, (a\cdot b)^2=a^2\cdot b^2$

陪集与拉格朗日定理

同态与同构

环和域

群码

第12章 格与布尔代数

格 交换 结合 吸收

格的性质和同态,逆关系 互为 对偶

分配格和有补格

格+分配+有界+有补 = 布尔格

总评

整理开始日期:2017-06-22

整理结束日期:[整理中 感觉技不如人甘拜下风 是不是用google翻译的啊]

ISBN 978-7-214-06747-0

千万不要买这本书,定义,证明的翻译看得我好难受,整理的时候心一万个草泥马飞过,感觉需要再搞一本英文版对着看

绪论

序号 定义
1 质量 物质的量就是物质的度量,可以通过物体的密度和体积而算出
2 动量 运动的量即是运动的度量,是由其速度和物质的量共同算出的
3 惯性 vis insita,或称为物体本身固有的力,是一种起抵抗作用的力。它存在于每一个物体之中,并始终使物体保持现有的静止或匀速直线运动的状态
4 施加在物体上的外力,其作用是使物体改变静止或匀速直线运动的状态。
5 向心力迫使物体趋向一个中心点,并对任何倾向于该点的物体起作用。
6 用向心力的绝对度量来量度向心力,它与由中心导致产生,并且通过周围空间传递出来的效能成正比
7 用向心力的加速度度量来量度向心力,它与向心力在一个给定时间里产生的速度成正比
8 用向心力的运动度量来量度向心力,它与向心力在一个给定时间里产生的运动量成正比

运动公理或定律

序号 定律
1 对于任何一个物体,除非有外力作用于它并改变其状态,否则它将保持静止或匀速直线的运动状态。
2 运动的变化与外力成正比,且沿着外力作用的直线方向进行变化
3 每一种作用都有一个与之方向相反的反作用,并且,两个物体间的相互作用总是相等的。
推论1 当两个力同时作用于一个物体时,这个物体将沿着平时四边形的对角线运动,所需时间等于两个力分别沿两边运动所用的时间之和。
推论2 由此,可以得到以下结论:一个直线力AD可以由任何两个倾斜力AC和CD合成。反之,任何一个直线力AD又可以分解成AC和CD两个倾斜力。
推论3 运动的量,是由同一方向的运动的和以及相反方向运动的差所得出,并且在物体间的相互作用保持不变
推论4 两个或多个物体共同的重力中心不会因为物体间的相互作用而改变其运动或静止的状态。如果将外力和阻碍作用排除在外,那么,所有相互作用的物体的公共重心不是静止就是处于匀速直线运动状态
推论5 在一个给定的空间中,物体的运动和它们自身之间的运动是一样的,无论此空间是静止的还是在做不含任何旋转运动的匀速直线运动。
推论6 无论物体相互之间以何种形式运动,在平行方向上,被相同的加速力加速时,都会继续之前相互间的运动,就如同没有加速力时一样

其它 插图

四种基本力:万有引力 电磁力维持原子 弱作用力释放射线 强作用力维持原子核


  • 物体的运动
    • 通过量的初值与终值的比率(引理1-11)
    • 向心力的确定
    • 物体在偏心圆锥曲线上的运动
    • 通过已知焦点求椭圆、抛物线和双曲线的轨道
    • 由未知焦点求曲线轨道
    • 如何求已知轨道上物体的运动
    • 物体的直线上升或下落
    • 如何确定物体受任意类型向心力作用运动的轨道
    • 物体沿运动轨道进行运动以及在回归点的运动
    • 物体在给定表面上的运动以及物体的摆动运动
    • 球体的吸引力
    • 非球状物体的吸引力
    • 受指向极大物体上各部分的向心力推动的极小物体的运动
  • 物体在阻滞介质中的运动
    • 受与速度成正比的阻力作用的物体的运动
    • 受与速度平方成正比的阻力作用的物体的运动
    • 受部分与速度成正比而部分与速度平方成正比的阻力作用的物体的运动
    • 物体在阻碍介质中的圆运动
    • 流体密度和压力 流体静力学
    • 摆体的运动及其受到的阻力
    • 流体的运动;流体施加于抛体的阻力
    • 通过流体传播的运动
    • 流体的圆运动
  • 宇宙体系 使用数学的论述
    • 哲学中的推理规则
    • 现象
    • 命题
    • 月球交汇点的运动

1.1.1

0%