看到一位拿到优秀同学的自我介绍和项目经历, 给人的感觉是稳扎稳打, 逻辑清晰, 不乏亮点. 反观自己, 虽然有丰富的学习经历, 但没有很好的挖掘和整理, 有些深度不够, 有些即使学过也是记忆不深. 所以想重新整理一遍, 希望有所获. 下面是看到那位同学分享的项目经验, 拿来作为范本学习. 后面会逐渐补充内容.
项目实践
项目名称为Linux下C++轻量级Web服务器开发,实现web端用户注册,登录功能,经压力测试可以实现上万的并发连接。(测试机器为Intel i7 7700,16G内存) 推荐学习1个月
线程池
涉及线程,锁机制。使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。
HTTP请求与响应
涉及Linux系统编程,网络编程,TCP和HTTP协议。根据状态转移,通过主从状态机封装了http连接类。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机。
注册登录
涉及少许网页html知识。实现用户名和密码校验,另外通过html文件设置action实现跳转。
定时器
涉及Linux系统信号及数据结构的使用。由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。
数据库连接池
涉及MySQL数据库。建立数据库连接池,通过重复使用这些已经建立的数据库连接,解决频繁建立连接的缺点,从而提高系统性能。
同步/异步日志系统
涉及设计模式,自定义阻塞队列。同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备。
压力测试
阅读Webbench源码,对进程加深理解。通过Webbench创建多个进程,每个进程通过HTTP连接访问服务器,完成压力测试。
清晰的结构既能体现出自己对项目全局的把控, 也能方便对方从各个方面考察. 下面是自己这个周末做的总结.
1. Java 8种基本类型占多少字节
double, long 64bit > 8字节
int, float 32bit > 4字节
short, char 65536种取值 > 2^16 > 2字节
boolean, byte > 1字节
2. 抽象与接口的区别
- 不能有abstract对象, 抽象方法不能有方法体, 抽象类可以有非抽象方法, 有抽象方法一定为抽象类, 抽象类的所有抽象方法必须在非抽象子类被重写, private abstract不能共存.
- 抽象类的起到了规范作用, 作为父亲, 规范了哪些方法必须存在.
- 接口方便类实现不同的属性, 接口中的方法默认修饰符为 public abstract. 接口中的成员变量默认 static, public, final
3. ThreadLocal的底层原理
这周末仔细看了ThreadLocal的底层代码, 在此简单梳理一遍 ThreadLocal是线程本地变量, 仔细想想, 线程内创建的变量其实都是本地变量, 那ThreadLocal又有什么特殊呢?
- ThreadLocal类中有个静态内部类: ThreadLocalMap, 而每个线程有唯一的一个ThreadLocalMap, 所以线程内每创建一个ThreadLocal, 就在ThreadLocalMap中增加一个Entry. 该Map为了处理large并且long-lived对象, 使用了WeakReference作为Entry, WeakReference 确保了线程终止之后,对应的线程变量即ThreadLocal实例能够被回收.
- ThreadLocalMap的Hash方式不同于HashMap的Hash+链表+红黑树, 它一方面使用”2^32*0.618”作为HashCode(这种方式能够均匀的分散数据分布, 减小碰撞), 另一方面当出现哈希碰撞了, 就自动往后移位(到达队尾时就回到0处), 所以在get时, 如果哈希位置取到的值不等于目标值, 也会自动往后移位判断.
- ThreadLocalMap在set, get, remove时, 如果出现了哈希碰撞, 就会检查index前后有没有老旧的对象
使用场景:
在线程上绑定一些变量的时候,非常有用;如果一个线程需要执行很多的操作,可能跨域了很多个方法、类级别的调用,同时可能耗时也比较长,ThreadLocal既能保证线程内为全局变量,又能保证线程安全。
4. 强引用, 软引用, 弱引用, 虚幻引用
- 强引用:
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfM moryError错误,使程序异常终止,也不会靠随意回收具有强引用 对象来解决内存不足的问题。
- 软引用
软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- 弱引用
弱引用也是用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
设计 WeakHashMap类是为了解决一个有趣的问题。如果有一个值,对应的Key已经不再使用了, 将会出现什么情况呢? 假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键 / 值 对无法从映射中删除。为什么垃圾回收器不能够删除它呢? 难道删除无用的对象不是垃圾回收器的工作吗? 遗憾的是,事情没有这样简单。垃圾回收器跟踪活动的对象。只要映射对象是活动的, 其中的所有桶也是活动的, 它们不能被回收。因此,需要由程序负责从长期存活的映射表中 删除那些无用的值。 或者使用 WeakHashMap完成这件事情。当对键的唯一引用来自散列条 目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。 下面是这种机制的内部运行情况。WeakHashMap 使用弱引用(weak references) 保存键。 WeakReference 对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的 对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象 已经没有他人引用了,就将其回收。然而, 如果某个对象只能由 WeakReference 引用, 垃圾 回收器仍然回收它,但要将引用这个对象的弱引用放人队列中。WeakHashMap将周期性地检 查队列, 以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用, 并 且已经被收集起来。于是, WeakHashMap将删除对应的条目。
- 虚引用 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。
jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用,这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。
5. RESTful架构是什么意思
- 每一个Uri代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层(可能是XML,Txt,Html,Json等等)
- 客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”
6. 垃圾回收
这里的图示非常清晰.
-
年轻代垃圾回收
-
CMS老年代垃圾回收
当老年代内存占用率达到一定比率时,CMS垃圾回收器就开始工作。 老年代垃圾回收会经历4个过程:
初始标记 :标记GC Roots能直接关联到的对象,需要在safepoint位置暂停所有执行线程。 并发标记 :进行GC Roots Tracing,遍历完从root可达的所有对象。该阶段与工作线程并发执行。 重新标记 :修正并发标记期间因用户程序继续运作而导致标记产生表动的那一部分对象的标记记录。需要在safepoint位置暂停所有执行线程。 并发清理 :内存回收阶段,将死亡的内存对象占用的空间增加到一个空闲列表(free list),供以后的分配使用。 重置 :清理数据结构,为下一个并发收集做准备。
内存回收整理后如下:整理之后释放了很多死亡的内存,但并没有进行压缩和整理。
CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,主要有三个显著缺点:cpu敏感,浮动垃圾,空间碎片
- G1垃圾回收器
G1(Garbage First)收集器是JDK1.7提供的一个新收集器,是当今收集器技术发展的最前沿成果之一。G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK1.5中发布的CMS收集器。 与其他GC收集器相比,G1具备如下特点: 1、并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。 2、分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能单独管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象已获得更好的收集效果。 3、空间整合:与CMS的“标记-清除”算法不同,G1收集器从整体上看是基于“标记-整理”算法实现的,从局部(两个Region之间)上看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序的长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。 4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
region介绍 大小一致,数值是在 1M 到 32M 字节之间的一个 2 的幂值数,JVM 会尽量划分 2048 个左右、同等大小的 region。当然这个数字既可以手动调整,G1 也会根据堆大小自动进行调整。 在G1实现中,一部分region 是作为Eden,一部分作为Survivor,除了意料之中的 Old region,G1 会将超过 region 50% 大小的对象归类为 Humongous 对象,并放置在相应的 region 中。逻辑上,Humongous region 算是老年代的一部分,因为复制这样的大对象是很昂贵的操作,并不适合新生代 GC 的复制算法。
G1的缺点: region 大小和大对象很难保证一致,这会导致空间的浪费;特别大的对象是可能占用超过一个 region 的。并且,region 太小不合适,会令你在分配大对象时更难找到连续空间,这是一个长久存在的情况。
GC 算法的角度,G1 选择的是复合算法,可以简化理解为: 在新生代,G1 采用的仍然是并行的复制算法,所以同样会发生 Stop-The-World的暂停。新生代的清理会带上old区已标记好的region。 在老年代,大部分情况下都是并发标记,而整理(Compact)则是和新生代GC时捎带进行,并且不是整体性的整理,而是增量进行的,也就是原本新生代的区域中对象在足够old时,该区域可以直接成为老生代。
Humongous 对象的分配和回收 Humongous region 作为老年代的一部分,通常认为它会在并发标记结束后才进行回收,但是在新版 G1 中,Humongous 对象回收采取了更加激进的策略。我们知道 G1 记录了老年代 region 间对象引用,Humongous 对象数量有限,所以能够快速的知道是否有老年代对象引用它。如果没有,能够阻止它被回收的唯一可能,就是新生代是否有对象引用了它,但这个信息是可以在 Young GC 时就知道的,所以完全可以在 Young GC 中就进行 Humongous 对象的回收,不用像其他老年代对象那样,等待并发标记结束。