Fighter's Blog

深度沉迷学习


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

Kubernetes 记录

发表于 2018-12-04 | 分类于 其它笔记

环境

  • 7台虚拟机,三master三node,一台作为Docker私库
  • VIP
    • 192.168.11.222
  • master
    • 192.168.11.31
    • 192.168.11.32
    • 192.168.11.33
  • node
    • 192.168.11.34
    • 192.168.11.35
    • 192.168.11.36
  • Harbor registry
    • 192.168.11.200
阅读全文 »

制作Docker镜像

发表于 2018-06-20 | 分类于 其它笔记

前言

因为最近在做大数据分析大作业,要求将算法封装进 Docker ,因此临时学了一下 Docker 方面的知识,在这里简单记录一下。

Docker 安装

Docker 安装比较简单,可以参考网上教程

Docker 架构

使用 Docker 前需要首先理解 Docker 的架构,因为以前自己只使用过 Vmware/VirtualBox 之类虚拟机,所以之前对 Docker 的理解也跟对 Vmware/VirtualBox 认识差不多,但学习之后发现他们还是有很大区别的, Docker 中融汇了面向对象的思想。网上有两张图对初学者很友好,让人很容易理解 Docker 架构思想:

  • Docker 使用客户端-服务器 (C/S) 架构模式,使用远程 API 来管理和创建 Docker 容器。
  • Docker 容器通过 Docker 镜像来创建。
  • 容器与镜像的关系类似于面向对象编程中的对象与类。
Docker 面向对象
容器 对象
镜像 类

理解了上面几句话,大概就能跟我一样简单使用了,Docker hub 仓库其实就相当于打包了很多常用的 Docker 镜像,我们可以直接 pull 下来,启动成容器来在本地跑,省去了很多搭环境浪费的时间。

阅读全文 »

使用VSCode搭建C/C++开发环境

发表于 2018-03-29 | 分类于 其它笔记

前言

本人虽然最近偏向Java语言,但平常有时也会遇到需要临时调试些C/C++代码,虽然电脑装了VS又感觉有点大材小用,而我平常一直是在用VSCode写markdown等,实际上VSCode和Sublime、Notepad++等都有很多插件可以装,装完就可以当作“短小精悍”的IDE啦,下面就记录下前天在Win10下用VSCode搭建C/C++环境的过程。

环境

  • Windows 10 64位
  • VSCode 1.21.1 64位
  • MinGW

搭建过程

安装C/C++插件

在 VSCode 左侧第5个插件商店下面搜索C/C++,出现第一个插件应该就是微软官方插件,长这样:

阅读全文 »

(四)图

发表于 2018-03-26 | 分类于 算法与数据结构

无向图

  • 图:是由一组顶点和一组能够将两个顶点相连的边组成的
  • 自环:即可一条龙连接一个顶点和其自身的边
  • 平行边:连接同一对顶点的两条边
  • 常将含有平行边的图称为多重图,而没有平行边或自环的图称为简单图
  • 相邻:当两顶点通过一条边相连时,称这两个顶点是相邻的,并称连接依附于这两个顶点
  • 度数:顶点的度数就是依附于它的边数
  • 子图:由所有边的一个子集以及这些边依附的所有顶点组成的图
  • 路径是由边顺序连接的一系列顶点;简单路径是一条没有重复顶点的路径
  • 环是一条至少含有一条边且起点和终点相同的路径;简单环(除了起始和终点必须相同外)是一条不含有重复顶点和边的环
  • 路径和环的长度为其中包含的边数
  • 连通图:若从任意一个顶点都存在一条路径到达另一个任意顶点;非连通图:由若干连通部分组成,它们都是其极大连通子图
  • 树是无环连通图;互不相连的树组成的集合称为森林;连通图的生成树是它的一幅子图,它含有图中所有顶点且是一棵树;图的生成树森林是它的所有连通子图的生成树的集合
  • 当且仅当V个结点的图G满足下列5个条件之一时是树
    • G有V-1条边且不含环
    • G有V-1条边且是连通的
    • G是连通的,但删除任意一条边都会使它不再连通
    • G是无环图,但添加任意一条边都会产生一条环
    • G中任意一对顶点之间仅存在一条简单路径
  • 二分图中每条边连接的两个顶点分别属于不同部分

图的数据结构

  1. 必须为可能在应用中碰到的各种类型的图预留出足够空间
  2. Graph 实例方法的实现一定要快
阅读全文 »

Java中的锁

发表于 2018-03-18 | 分类于 Java笔记

这部分主要介绍Java并发包中与锁相关的API和组件的使用和实现。

Lock接口

Lock接口之前Java是靠synchronized关键字实现锁功能的,而Java SE 5后并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽没有了隐式释放锁的便利性,但有了如下优点:

  1. 锁获取与释放的可操作性
  2. 可中断的获取锁
  3. 超时获取锁等

Lock使用方式:

1
2
3
4
5
6
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
阅读全文 »

(四)Java并发编程基础

发表于 2018-03-14 | 分类于 Java笔记

线程简介

现代操作系统调度的最小单元是线程,也叫轻量级进程(LightWeight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MultiThread {
public static void main(String[] args) {
// 获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
}
}
}
//输出
[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main

为什么使用线程

  1. 更多处理器核心
  2. 更快响应时间
  3. 更好的编程模型
    阅读全文 »

(三)Java内存模型

发表于 2018-03-04 | 分类于 Java笔记

Java 内存模型的基础

并发编程模型的两个关键问题

  1. 线程之间如何通信(线程之间以何种机制来交换信息)
  2. 线程之间如何同步(程序中用于控制不同线程间操作发生相对顺序的机制)

在命令式编程中,线程之间通信机制有两种:共享内存和消息传递。

  1. 共享内存的并发模型
    • 线程通信:线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信
    • 线程同步:同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
  2. 消息传递的并发模型
    • 线程通信:线程之间没有公共状态,线程间必须通过发送消息来显式进行通信
    • 线程同步:由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。

Java 并发采用共享内存模型,Java 线程间通信总是隐式进行,整个通信过程对程序员完全透明。

阅读全文 »

(二)Java并发机制的底层实现原理

发表于 2018-03-02 | 分类于 Java笔记

Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。

volatile 应用

volatile 是轻量级 synchronized,它在多处理器开发中保证了共享变量的“可见性”。

可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改后的值。

若 volatile 变量修饰符使用恰当,它比 synchronized 使用和执行成本更低,因为它不会引起线程上下文切换和调度。

volatile 定义与实现原理

Java 语言规范对 volatile 的定义:Java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应确保通过排它锁单独获得这个变量。Java 语言提供了 volatile,在某些情况下比锁更加厉害。若一个字段被声明为 volatile,Java 线程内存模型确保所有线程看到这个变量的值是一致的。

阅读全文 »

(一)并发编程的挑战

发表于 2018-03-02 | 分类于 Java笔记

上下文切换

单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。

上下文切换:任务从保存到再加载的过程就是一次上下文切换。

多线程一定快吗?

不一定,因为线程有创建和上下文切换开销。

减少上下文切换

方法:

  • 无锁并发编程
    多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法来避免使用锁,如将数据的 ID 按 Hash 算法取模分段,不同线程处理不同段数据。
  • CAS 算法
    Java 的 Atomic 包使用 CAS 算法来更新数据,而不需要加锁。
  • 使用最少线程和使用协程
    • 避免创建不需要的线程,比如任务很少,但创建了很多线程来处理,这会造成大量线程都处于等待状态
    • 协程:在单线程里实现多任务的调度,并在单线程里维持多任务间的切换
      阅读全文 »

类加载

发表于 2018-02-24 | 分类于 Java笔记

类加载机制

概述

  • 虚拟机把描述类的数据从 class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java 类型,这就是虚拟机的类加载机制。
  • 懒加载

类加载时机

初始化

  1. 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时,如果类没有进行初始化,则需要先触发其初始化。生成这 4 条指令的最常见的 Java 代码场景是:使用 new 关键字实例化对象的时候、读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候,
  2. 使用 Java.lang.refect 包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先执行该主类。

不被初始化的例子

  • 通过子类引用父类的静态字段,子类不会被初始化
  • 通过数组定义来引用类
  • 调用类的常量

类加载的过程

加载

  • 通过一个类的全限定名来获取定义此类的二进制流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 class 对象,作为这个类的各种数据的访问入口
加载源
  • 文件
    • class 文件
    • jar 文件
  • 网络
  • 计算生成一个二进制流
    • $Proxy
  • 由其它文件生成
    • jsp
  • 数据库

验证

  • 验证是连接的第一步,这一阶段的目的是为了确保 class 文件的字节流中包含的信息符合当前虚拟机的要求。并且不会危害虚拟机自身的安全。
  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备

  • 准备阶段正式为类变量分配内存并设置变量的默认值。这些变量使用的内存都将在方法区进行分配。如果变量被 final 修饰,则在这个过程中,常量值会被一同指定。

解析

  • 解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
  • 类或者接口的解析
  • 字段解析
  • 类方法解析
  • 接口方法解析

【深入理解JVM】:类加载机制

初始化

  • 初始化是类加载的最后一步
  • 初始化是执行 <clinit>() 方法的过程

    类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。
    在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器方法的过程。
    【深入理解JVM】:类加载机制
    Java虚拟机类加载机制
    类加载机制
    【Java面试题】之类加载:从面试题分析Java类加载机制

类加载器

  • 虚拟机的设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称之为类加载器。
  • 只有被同一个类加载器加载的类才可能会相等。相同的字节码被不同的类加载器加载的类不相等。

类加载器分类

  • 启动类加载器
    • 由 C++ 实现,是虚拟机的一部分,用于加载 javahome 下的 lib 目录下的类
  • 扩展类加载器
    • 加载 javahome 下 /lib/ext 目录中的类
  • 应用程序类加载器
    • 加载用户类路径上的所指定的类库
  • 自定义类加载器
    • 定义一个类,继承 classloader
    • 重写 loadclass 方法
    • 实例化 class 对象

自定义类加载器的优势

  • 类加载器是 Java 语言的一项创新,也是 Java 语言流行的重要原因之一,它最初的设计是为了满足 Java Applet 的需求而开发。
  • 高度灵活性
  • 通过自定义类加载器可以实现热部署
  • 代码加密

双亲委派模型

  • 从 jdk1.2 开始,java 虚拟机规范推荐开发者使用双亲委派模型(ParentsDelegation Model)进行类加载,其加载过程如下:
  • 如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成
  • 每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器
  • 如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出 ClassNotFoundException,而不再调用其子类加载器去进行类加载。
  • 双亲委派模型的类加载机制的优点是 java 类 它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了 java 程序的稳定运行。

虚拟机字节码执行引擎

  • 字节码结构
  • 类加载
  • 虚拟机字节码执行引擎

运行时栈帧结构

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法返回地址
  • 附加信息

[深入理解JVM 六]—-虚拟机字节码执行系统
Java方法执行
【深入Java虚拟机】之五:多态性实现机制——静态分派与动态分派

局部变量表
1
2
3
4
5
6
7
8
//实例变量有默认值,即便不赋值也可以直接使用
private int a;
private int b;
add(){
int a, b;
return a + b;//没有赋值,报错
}
  • slot 32 64
  • byte、boolean、short、char、int、float、double、long(不会出现线程安全性问题)、reference(指向对象地址)、returnAddress(不再使用)

线程安全性问题三要素:

  1. 多线程
  2. 要有共享资源
  3. 对共享资源进行非原子性操作
1
2
int a = 10;
a--;// a = a - 1;
  • slot 复用:当一个变量的 pc 寄存器的值大于 slot 的作用域的时候,slot 是可以复用的
1
2
3
4
public static void main(String[] args){
byte[] buff = new byte[60 * 1024 * 1024];
System.gc();//不回收
}
1
2
3
4
5
6
7
public static void main(String[] args){
{
byte[] buff = new byte[60 * 1024 * 1024];
}
int a = 10;//对局部变量表读写了
System.gc();//回收,因此引用使用完使其为 null,方便垃圾回收
}

局部变量表Slot复用

操作数栈

每个栈帧不完全独立,实际上局部变量表的部分数据重叠
栈帧、局部变量表、操作数栈

动态连接
方法返回地址和附加信息
  • 方法返回地址:调用时通过一个指向方法的指针指向方法的地址,方法返回时将回归到调用处,那个地方是返回地址。
  • 附加信息:虚拟机规范中允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中。这部分信息完全取决于虚拟机的实现。

方法调用

  • 解析调用
  • 分派调用
    • 静态分派调用
    • 动态分派调用
  • 动态语言支持

  • 方法调用并不等同于方法的执行,方法调用阶段的唯一任务就是确定被调用方法的版本

  • 封装、继承、多态

类加载过程中的解析作用:将 class 文件中的符号引用解析成直接引用

解析调用

静态方法、构造器方法、私有方法和被 final 修饰的方法不能被重写、重载,编译完就能确定版本

  • invokestatic(静态方法的字节码)
  • invokespecial(构造器方法、私有方法、父类方法生成的字节码)
  • invokevirtual
  • invokeinterface
  • invokedynamic

静态分派调用(单、多分派)

静态分派调用主要针对方法的重载(编译期确定?):

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
public class StaticCall {
static class Parent {
}
static class Child1 extends Parent{}
static class Child2 extends Parent{}
public void sayHello(Child1 c1){
System.out.println("c1 is called");
}
public void sayHello(Child2 c2){
System.out.println("c2 is called");
}
public void sayHello(Parent p){
System.out.println("p is called");
}
public static void main(String[] args) {
//=左边静态类型 右边实际类型
Parent p1 = new Child1();
Parent p2 = new Child2();
StaticCall s = new StaticCall();
s.sayHello(p1);
s.sayHello(p2);
//实际类型发生改变
Parent p = new Child1();
p = new Child2();
//静态类型发生改变
s.sayHello((Child2)p);
}
}

方法调用时会选一个最先匹配的执行:

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
public class StaticCallTest2 {
public void sayHello(byte a){
System.out.println("byte");
}
public void sayHello(short a){
System.out.println("short");
}
public void sayHello(int a){
System.out.println("int");
}
public void sayHello(long a){
System.out.println("long");
}
public void sayHello(char a){
System.out.println("char");
}
public void sayHello(Character a){
System.out.println("Character");
}
public void sayHello(Object a){
System.out.println("Object");
}
public void sayHello(char ... a){
System.out.println("char ...");
}
public static void main(String[] args) {
StaticCallTest2 s = new StaticCallTest2();
s.sayHello('1');
}
}

动态分派调用(单、多分派)

动态分派调用针对方法的重写:

  • 找到操作数栈顶的第一个元素所指向的对象的实际类型
  • 如果在实际类型中找到与常量池中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则直接返回这个方法的直接引用,查找过程结束,如果不通过,抛出异常
  • 按照继承关系从下往上依次对实际类型的各父类(父类编译期就可知道)进行搜索与验证
  • 如果始终没有找到,则抛出 AbstractMethodError

动态类型语言支持

  • 静态类型的语言在非运行阶段,变量的类型是可以确定的,也就是说变量是有类型的
  • 动态类型语言在非运行阶段,变量的类型是无法确定的,也就是变量是没有类型的,但是值是有类型的,也就是运行期间可以确定变量的值的类型

字节码执行引擎小结

  • 代码执行时涉及的内存结构
  • 如何找到正确的执行方法
  • 如何执行方法的字节码

总结

  • Java 技术体系
    • Java 程序设计语言
    • Java 虚拟机
    • class 文件格式
    • Java API
    • 第三方类库

  • Java 技术体系,认识 Java 虚拟机
  • 内存管理
    • 运行时内存区域
    • 对象的创建,内存布局以及访问定位
    • 垃圾回收
      • 判断对象是否存活算法
      • 垃圾收集算法
      • 垃圾收集器
    • 内存的分配
    • Java 的监控工具
  • Java 虚拟机的运行
    • class 文件格式
    • class 字节码指令
    • 类加载
    • 字节码执行引擎
123
Fighter.

Fighter.

学习 | 分享 | 交流 | 进步

26 日志
6 分类
28 标签
GitHub Weibo
© 2016 - 2018 Fighter.
   |    Hosted by GitHub Pages