Star's Blog

Keep learning, Keep improving


  • 首页

  • 分类

  • 关于

  • 标签

  • 归档

  • 搜索

Java虚拟机的类加载机制

发表于 2018-09-03 | 分类于 Java虚拟机

Java程序的运行时需要JVM支持的,而在JVM的运行过程中,需要把所有的Java类加载到虚拟机中才能运行。本文介绍了JVM的类加载机制。

类的生命周期与加载时机

类的生命周期

一个类从被加载到虚拟机内存中开始,到被卸载出内存为止,整个生命周期包括了 加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中 验证、准备、解析 3部分统称为链接,如下图:

整个顺序并不是完全固定的,其中解析阶段可以在初始化之后再开始,这样便可以实现Java的运行时绑定(动态绑定)机制。

类的加载时机

JVM虚拟机规范并没有对类的加载时机做出严格的要求,只规定了以下五种情况需要立刻触发类的初始化:

其余条件下,可以由JVM虚拟机自行决定何时去加载一个类。

    • 遇到new,getstatic,putstatic和invokestatic这四个字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。
    • 使用反射机制对类进行调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    • 当初始化一个类时,如果其父类还没有进行过初始化,则需要先触发其父类的初始化。
    • 虚拟机启动时,用户需要指定一个要执行的主类(包含main方法),此时会先初始化这个类
    • 使用JDK1.7的动态语言支持时,如果一个MethodHandle实例最后的解析结果包含REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,且这个方法句柄对应的类没有初始化,则需要先对其进行初始化。

主动引用和被动引用

上面五种条件也被称为对类的主动引用,除此之外其他引用类的方式都不会触发初始化,即类的被动引用,举个例子:

阅读全文 »

为什么分布式一定要有Redis?

发表于 2018-09-01 | 分类于 数据库

大部分写业务的程序员,在实际开发中使用 Redis 的时候,只会 Set Value 和 Get Value 两个操作,对 Redis 整体缺乏一个认知。

本文围绕以下几点进行阐述:

  • 为什么使用 Redis
  • 使用 Redis 有什么缺点
  • 单线程的 Redis 为什么这么快
  • Redis 的数据类型,以及每种数据类型的使用场景
  • Redis 的过期策略以及内存淘汰机制
  • Redis 和数据库双写一致性问题
  • 如何应对缓存穿透和缓存雪崩问题
  • 如何解决 Redis 的并发竞争 Key 问题

为什么使用 Redis

我觉得在项目中使用 Redis,主要是从两个角度去考虑:性能和并发。

当然,Redis 还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件,如 ZooKeeper 等代替,并不是非要使用 Redis。因此,这个问题主要从性能和并发两个角度去答。点击这里查看Redis面试题汇总。

性能

如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

题外话:忽然想聊一下这个迅速响应的标准。根据交互效果的不同,这个响应时间没有固定标准。

不过曾经有人这么告诉我:“在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。”

那么瞬间、刹那、一弹指具体是多少时间呢?

根据《摩诃僧祗律》记载:

一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。

那么,经过周密的计算,一瞬间为 0.36 秒、一刹那有 0.018 秒、一弹指长达 7.2 秒。

并发

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。

这个时候,就需要使用 Redis 做一个缓冲操作,让请求先访问到 Redis,而不是直接访问数据库。点击这里查看Redis面试题汇总。

使用 Redis 有什么缺点

大家用 Redis 这么久,这个问题是必须要了解的,基本上使用 Redis 都会碰到一些问题,常见的也就几个。

回答主要是四个问题:

  • 缓存和数据库双写一致性问题
  • 缓存雪崩问题
  • 缓存击穿问题
  • 缓存的并发竞争问题

这四个问题,我个人觉得在项目中是常遇见的,具体解决方案,后文给出。

单线程的 Redis 为什么这么快

这个问题是对 Redis 内部机制的一个考察。根据我的面试经验,很多人都不知道 Redis 是单线程工作模型。所以,这个问题还是应该要复习一下的。

回答主要是以下三点:

  • 纯内存操作
  • 单线程操作,避免了频繁的上下文切换
  • 采用了非阻塞 I/O 多路复用机制

题外话:我们现在要仔细的说一说 I/O 多路复用机制,因为这个说法实在是太通俗了,通俗到一般人都不懂是什么意思。

打一个比方:小曲在 S 城开了一家快递店,负责同城快送服务。小曲因为资金限制,雇佣了一批快递员,然后小曲发现资金不够了,只够买一辆车送快递。

经营方式一

客户每送来一份快递,小曲就让一个快递员盯着,然后快递员开车去送快递。

慢慢的小曲就发现了这种经营方式存在下述问题:

  • 几十个快递员基本上时间都花在了抢车上了,大部分快递员都处在闲置状态,谁抢到了车,谁就能去送快递。
  • 随着快递的增多,快递员也越来越多,小曲发现快递店里越来越挤,没办法雇佣新的快递员了。
  • 快递员之间的协调很花时间。

综合上述缺点,小曲痛定思痛,提出了下面的经营方式。

阅读全文 »

遍历集合时删除元素,到底发生了什么

发表于 2018-08-30 | 分类于 数据结构

开发规范里写的非常清楚,当通过 for 循环遍历集合时,一般禁止操作 (add or remove) 集合元素。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
List<String> list = new ArrayList<>();
list.add("e1");
list.add("e2");
for(String str : list) {
if("e1".equals(str)) {
list.remove("e1");
}
if("e2".equals(str)) {
System.out.println("element 2 fetched");
}
}

运行结果:element 2 fetched 将不会被打印。

字节码中是如何处理的?

让我们看看字节码是怎么样的,仅截图了部分字节码。

如上面截图的 #27、#34、#43,foreach 实际上是通过 Iterator 来处理的。最后通过 #87 的 goto指令进入下一次遍历,并进行 hasNext()判断。

class文件反编译后又是怎么样的?

再来看看将.class文件反编译后得到的代码,实际上编译器将 foreach 转换成了用 Iterator来处理。

所以,眼见不一定为实,程序员开发时用的是高级语言,编码怎么简单高效怎么来,所以偶尔也可以看看反编译class后的代码以及字节码文件,看看编译器做了哪些优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
ArrayList list = new ArrayList();
list.add("e1");
list.add("e2");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String str = (String)var2.next();
if("e1".equals(str)) {
list.remove("e1");
}
if("e2".equals(str)) {
System.out.println("element 2 fetched");
}
}

为什么remove(e1)会导致无法获取e2?

当 list.remove(“e1”)后,在 while(var2.hasNext()) 时,返回结果将为 false,因此当循环一次后Iterator将认为list已经遍历结束。

要弄清原因,需要看看ArrayList对于Iterator接口的实现,了解hasNext()、next()方法的实现。

先看看ArrayList中实现Iterator的内部类Itr。

阅读全文 »

JAVA对象引用,对象赋值

发表于 2018-08-27 | 分类于 Java基础

关于对象与引用之间的一些基本概念。

​ 初学Java时,在很长一段时间里,总觉得基本概念很模糊。后来才知道,在许多Java书中,把对象和对象的引用混为一谈。可是,如果我分不清对象与对象引用,那实在没法很好地理解下面的面向对象技术。把自己的一点认识写下来,或许能让初学Java的朋友们少走一点弯路。

​ 为便于说明,我们先定义一个简单的类:

1
2
3
4
5
public class Vehicle {
int passengers;
int fuelcap;
int mpg;
}

​ 有了这个模板,就可以用它来创建对象:

1
Vehicle veh1 = new Vehicle();

通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。

​ 1)右边的“new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle类对象(也简称为Vehicle对象)。

​ 2)末尾的()意味着,在对象创建后,立即调用Vehicle类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。

​ 3)左边的“Vehicle veh 1”创建了一个Vehicle类引用变量。所谓Vehicle类引用,就是以后可以用来指向Vehicle对象的对象引用。

​ 4)“=”操作符使对象引用指向刚创建的那个Vehicle对象。

我们可以把这条语句拆成两部分:

1
2
Vehicle veh1;
veh1 = new Vehicle();

效果是一样的。这样写,就比较清楚了,有两个实体:一是对象引用变量,一是对象本身。

​ 在堆空间里创建的实体,与在数据段以及栈空间里创建的实体不同。尽管它们也是确确实实存在的实体,但是,我们看不见,也摸不着。不仅如此,我们仔细研究一下第二句,找找刚创建的对象叫什么名字?有人说,它叫“Vehicle”。不对,“Vehicle”是类(对象的创建模板)的名字。

​ 一个Vehicle类可以据此创建出无数个对象,这些对象不可能全叫“Vehicle”。

​ 对象连名都没有,没法直接访问它。我们只能通过对象引用来间接访问对象。

​ 为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球。

​ 如果只执行了第一条语句,还没执行第二条,此时创建的引用变量veh1还没指向任何一个对象,它的值是null。引用变量可以指向某个对象,或者为null。

​ 它是一根绳,一根还没有系上任何一个汽球的绳。执行了第二句后,一只新汽球做出来了,并被系在veh1这根绳上。我们抓住这根绳,就等于抓住了那只汽球。

​ 再来一句:

1
Vehicle veh2;

​ 就又做了一根绳,还没系上汽球。如果再加一句:

1
veh2 = veh1;

系上了。这里,发生了复制行为。但是,要说明的是,对象本身并没有被复制,被复制的只是对象引用。结果是,veh2也指向了veh1所指向的对象。两根绳系的是同一只汽球。

​ 如果用下句再创建一个对象:

1
veh2 = new Vehicle();

​ 则引用变量veh2改指向第二个对象。

​ 从以上叙述再推演下去,我们可以获得以下结论:

(1)一个对象引用可以指向0个或1个对象(一根绳子可以不系汽球,也可以系一个汽球);

(2)一个对象可以有N个引用指向它(可以有N条绳子系住一个汽球)。

​ 如果再来下面语句:

1
veh1 = veh2;

​ 按上面的推断,veh1也指向了第二个对象。这个没问题。问题是第一个对象呢?没有一条绳子系住它,它飞了。多数书里说,它被Java的垃圾回收机制回收了。这不确切。正确地说,它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。

​ 由此看来,下面的语句应该不合法吧?至少是没用的吧?

1
new Vehicle();

​ 不对。它是合法的,而且可用的。譬如,如果我们仅仅为了打印而生成一个对象,就不需要用引用变量来系住它。最常见的就是打印字符串:

1
System.out.println(“I am Java!”);

​ 字符串对象“I am Java!”在打印后即被丢弃。有人把这种对象称之为临时对象。

​ 对象与引用的关系将持续到对象回收。

阅读全文 »

JAVA实现单链表

发表于 2018-08-26 | 分类于 数据结构

​ 刚开始学习java不久的时候以为java没有指针。。。不知道怎么弄链表,java中有基本数据类型和引用数据类型(其实就是指针)。如果对引用不够了解请访问 http://zwmf.iteye.com/blog/1738574 (写得特别好,就没必要重复了)。

实现链表的思路:

  1)链表类,结点类(链表类的内部类),在main()方法创建一条链表类对象,通过方法逐步创建结点类,通过引用链接起来成为链表。

  2)结点类包含数据和对下个结点的引用,以及可以对数据赋值的构造函数。

  3)链表类的构造方法,只构造出不含数据的头结点。(外部类可以直接对内部类的私有成员进行访问,这样就可以直接修改引用)

主体代码:
1
2
3
4
5
6
public  class MyLink<E>{
private class Node{
privare Object data; //保存数据
private Node next; //对下个结点的引用
}
}
完整版代码:
阅读全文 »
1…161718…20
Morning Star

Morning Star

100 日志
14 分类
37 标签
GitHub
© 2021 Morning Star
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4