招聘需求系列之三(加个提示目前还是中级的需求,后期会补高级的需求包括笔试面试题的系列)

3.1能够熟练使用Linux,并在Linux部署相关应用 

关于LINUX,是博主的弱项,因为工作中用的很少,基本都是在WINDOWS下开发,LINUX使用更多的是查看日志,发包重启这样的功能,但是LINUX也很好补,经常用嘛肯定就熟悉了,之前看过一阵子但是不经常去用那些命令也就忘了。推荐个网站-实验楼,很好的平台让你去操作LINUX系统完成一系列任务。
这个就这样吧,博主还没去纯用LINUX的公司体验过。实际工作中博主也给公司的服务器装过WINDOWS企业版和CENTOS,但是搭建环境的工作没做过,都是有专门的组负责公司的环境,需要的时候去他们那里申请,比如之前搞REDIS的时候去申请的服务器和REDIS库。
主要知识点是各种目录是干嘛,常用查看日志命令,搜索等,杀进程之类,重启服务器我们是有SHELL脚本的。

3.2解JVM性能调优、了解常见JVM垃圾收集算法、Java并发框架与库、了解Java内存模型

这个就需要好好看好好谈谈了,因为笔试面试的时候不用想,肯定有!这个会写很多很长,博主也会一边看书一遍写完。所以会有多次编辑修改的记录。

首先是调优,为什么调优?肯定是系统卡顿或者内存频繁溢出,排除了数据库和IO的问题后对JVM进行调优
而一般情况下是代码的问题而不是JVM的问题

这条需求包含的内容太多,我们一步步分解着来

1Java并发框架与库

库就是JDK1.5后新加入的java.util.concurrent 这个在博主之前看的《JAVA并发编程-从入门到精通》中有过介绍,博主这方面经验比较少,工作中用乐观锁以及同步代码块的情况居多。
至于并发框架博主就不知道了,百度出来的并发框架都没见过,以后看到学到用到相关的内容博主会补充的。

2Java内存模型

java-memory-model
每个线程有个自己的工作内存,然后有个共用的主内存,共享变量存在主内存中,工作内存存的是共享变量的副本,这就有一个主内存与工作内存的一致性问题也就是常见并发问题的由来,而volatile关键字就是告诉线程 每次用到这个共享变量,直接从主内存取并且有修改后立刻提交到主内存保障了一致性,但是带来了性能的损耗。
这里要注意一点,既然这个volatile修饰的值是共享的也就是说他要是静态的是属于这个类的,注意volatile要在static前面。
注意这个本地内存只是一个抽象概念,实际并不存在。
内存可见性就是JMM通过控制主内存与本地内存的交互达到的。

JMM还有一个重要概念:重排序。
编译后的JAVA文件的执行顺序是会被改变的,首先就是编译器的优化,然后是处理器的优化,最后是内存的优化。这些优化都会打乱执行的顺序,所以叫重排序,最后生成的顺序才回去执行,而这些重排序可能会导致并发问题,也就是内存可见性问题。所以JMM通过一系列保护机制确保可见性。
Java内存模型把内存屏障分为LoadLoad、LoadStore、StoreLoad和StoreStore四种。
屏障类型指令示例说明
LoadLoad BarriersLoad1; LoadLoad; Load2确保Load1数据的装载,之前于Load2及所有后续装载指令的装载。
StoreStore BarriersStore1; StoreStore; Store2确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。
LoadStore BarriersLoad1; LoadStore; Store2确保Load1数据装载,之前于Store2及所有后续的存储指令刷新到内存。
StoreLoad BarriersStore1; StoreLoad; Load2确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。

StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)。

happens-before

从JDK5开始,java使用新的JSR -133内存模型(本文除非特别说明,针对的都是JSR- 133内存模型)。JSR-133提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。 与程序员密切相关的happens-before规则如下:

  • 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
  • 监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
  • 传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。


3JVM内存模型

明天补充,睡了。
今天很晚才补充,因为博主打算自考个计算机应用专业的本科。虽然之前有统招本科文凭但是不是计算机专业的。经过各种百度终于把流程和需求弄清了。看起来真的很难,可能要几年时间才能下来,但是自己为了计算机专业的文凭,含着泪也要扛下去不是么?


再次重点提示一点,网上的东西虽然一百度一堆,但是大都笼统不成体系,去看书会好很多,真心的!
我们说JVM内存模型,这个JVM是目前JDK默认的HOTSPOT这个JVM。主要是用来说分代,分代是为了GC。
所以就是HOTSPOT的垃圾回收算法将堆分代,提到堆了就要系统说一下JVM的内存管理了。

在看JVM的内存管理前博主又恰好去看了CLASSLOADER,其中有一段是说动态类加载的
就是使用不同的CLASSLOADER加载类实在动态类加载时,这个字节码是保存在JVM的PERMGEN去的,而这个区的数据只有FULLGC时候才会被回收,所以大量动态类加载又没有经常的FULLGC就会可能导致内存溢出了!

首先我们要知道指令集的概念,能被JVM自己解析执行的指令集就是JVM自己的字节码指令集,class文件规范里的字节码都可以被JVM执行。
TOMCAT运行需要JVM,TOMCAT有CLASSLOADER,JVM也有,JVM还有执行引擎相当于CPU,同样会有自己模拟出的类似内存的结构以及本地方法调用JNI。
没一个JAVA线程就是一个执行引擎的实例。
JVM是基于栈的,原因有3:平台无关,优化代码和指令的紧凑性。
有内存肯定要有人使用它,这6个地方会需要使用内存:堆(存储对象的),线程(每个线程JVM都会为其创建一个堆栈,大小在256-756KB之间),类和类加载器(这个上面说过是存在PERMGEN永久代中的,而且任何类和类加载器加载的任何类都不能在运行时释放,因为无法满足一个类能被卸载的条件),NIO(就是不咋地被各种替代的那个NIO),JNI。

JAVA的内存结构
首先内存要存放数据,JVM内存中会存放6种数据:PC寄存器数据,JAVA栈(与线程息息相关上面说到的256-756KB的空间就是JVM为线程创建的堆栈,这个堆栈里又会有很多FRAMES,栈帧,每个方法会创建一个栈帧,每个栈帧又会含有部分内部变量,操作栈和方法返回值等信息。由于JAVA栈与线对应且不是线程共享的,不会有数据一致性与同步锁问题。栈速度比堆快,仅次于寄存器),堆(上面说了,存JAVA对象的地方,这个对象是这个对象的类的一个副本,会复制包括继承自他父类的所有非静态属性。堆是所有线程共享的,会有一致性问题),方法区(存储类的结构信息也属于堆,也即是PermGen永久代,既然属于堆同样被所有线程共享

方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,可见方法区域的重要性。同样,方法区域也是全局共享的,它在虚拟机启动时在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代,默认为64M,可通过-XX:PermSize以及-XX:MaxPermSize来指定其大小。

),本地方法栈和运行时常量池(就是面试题里String 放值的地方)。

这里的分代指明了什么时候需要进行GC,而GC的算法在下面有。
好了终于到了堆的分代了,该垃圾回收算法的思路是把对象按照寿命长短来分组,分为年轻代和年老代,新创建的都在年轻代,如果几次垃圾回收后还存活就转移到年老代,年老代的回收频率没那么频繁,
实际上的划分是:年轻代包括EDEN和SURVIVOR区,其中SURVIVOR区分为1 2也叫FROM和TO,年老代,永久代。
首先EDEN去满了的时候,会触发minor GC,然后把EDEN所有还活着的转移到SURVIVOR区中,由于MINOR GC包括整个年轻代,所以SURVIVOR的一个区实际上也进行了GC,剩下部分存活对象,之所以说是一个S区是因为S区始终会保持一个为空,那么EDEN活着的和S区活着的一起放入一个S区中,当S区也满了就转移到了年老代,如果经过几次GC年轻区的数据很老了也会转移到年老代。当年老代也满了,就会触发FULL GC了,FULL GC一般比MINOR GC慢10倍以上,所以我们是不希望看到FULL GC更别提频繁的FULL GC了。PERMGEN区满了也会触发FULL GC,也只有FULL GC能清理永久代了。

SUN建议堆中年轻代大小应为整个堆的1/4 而其中的S区每个区应为整个年轻代的8分之一也就是整个堆的1/32

4垃圾收集算法

首先JVM确定一个对象是垃圾有两种算法
1引用计数法
顾名思义,有引用就+1,但是一些特殊情况照顾不到,比如循环引用(A引用B,B引用A,但是AB都已经不被其他对象引用)
2根搜索方法
从几个根开始向下搜索,达不到的对象即是垃圾
下面的都是根

a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。

b) 方法区域中的类静态属性引用的对象。

c) 方法区域中常量引用的对象。

d) 本地方法栈中JNINative方法)的引用的对象。

通过上面的方法找到垃圾后就要清理,垃圾清理的算法有
1标记-清除算法
顾名思义,两个阶段,缺点是清除后会产生大量不连续的空间,如果这时候要放大对象可能找不到位置, 需要暂停整个应用
2 复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。

复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的JVM用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1(大概是8:1)

垃圾回收前:


垃圾回收后:


绿色:存活对象 红色:可回收对象 白色:未使用空间


3标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。

标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。

4.分代收集(Generational Collection

分代收集是根据对象的存活时间把内存分为新生代和老年代,根据个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用标记—复制算法,老年代采用标记—整理算法。

垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。上面介绍的只不过是基本思想。


上面这部分算法是从其他博客转过来的,而且垃圾回收算法根据角度不同还有很多种,因为JVM规范只是说你要有所作为在堆满了的时候,没说到底怎么做,所以是八仙过海各显神通,


    上面这四种是最基本的垃圾回收算法,在这四种算法思想之上发展出来其余算法,个人认为更多的是一种内存管理策略,甚至可以看成就是基本算法的组合应用,就像加减乘除与方程式的关系一样,因此将其分开描述。


    渐进式算法

    这类算法一般由基本算法组成,其核心思想是将内存分区域进行管理,在不同的区域和不同的时间采用不同的内存管理策略,从而避免因全系统的垃圾回收导致程序长时间暂停。这类算法一般有以下几种:

    1.火车算法

    这种算法把成熟的内存空间划为固定长度的内存块,算法每次在一个块中单独执行,每一个块属于一个集合。此算法的具体执行步骤较复杂,且没有具体的应用场景,在此不浪费笔墨,有兴趣的同学可以自己研究之。

    2.分代收集算法

    这种算法在sun/oracle公司的Hotspot虚拟机中得到应用,是java程序员需要重点关注的一种算法。

    这种算法是通过对对象的生命周期进行分析后得出的,它将堆内存分成了三个部分:年青代,年老代,持久代(相当于方法区)。处于不同生命周期的对象被存储于不同的区域,并使用不同的算法进行回收。这种算法的具体执行细节将会在后续的学习笔记中详细介绍。


    从网上得到的资料来看,应该还有一种增量收集算法,但是描述过于模糊,且没有标明出处,查不到相关资料,marker下,回头如有发现再回来补之。


    另外需要特别注意的是利用多线程实现的堆垃圾回收技术,这方面资料来源于网络,摘抄如下:


    1.串行收集

    串行收集使用单线程处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。

    适用情况:数据量比较小(100M左右);单处理器下并且对响应时间无要求的应用。

    缺点:只能用于小型应用。


    2.并行收集

    并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。

    适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。

    缺点:应用响应时间可能较长 。


    3.并发收集

    相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。

    适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。





在其他博客看到这段话觉得非常好

在调优之前,我们需要记住下面的原则:

 

1、多数的Java应用不需要在服务器上进行GC优化;

2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;意味着一般的参数设置足够用了

3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);

4、减少创建对象的数量;

5、减少使用全局变量和大对象;

6、GC优化是到最后不得已才采用的手段;意味着JVM自带的GC策略已经足够用了

7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;


5JVM调优

Q:为什么崩溃前垃圾回收的时间越来越长?

   A:根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据

   Q:为什么Full GC的次数越来越多?

   A:因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收

   Q:为什么年老代占用的内存越来越大?

   A:因为年轻代的内存无法被回收,越来越多地被Copy到年老代


调优是为了1减少年老代空间2减少FULLGC时间

为了达到上面的目的,一般地,你需要做的事情有:

1、减少使用全局变量和大对象;

2、调整新生代的大小到最合适;

3、设置老年代的大小为最合适;

4、选择合适的GC收集器;


性能调优方法:

1线程池

2连接池

3启动参数

(1)针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
   (2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

   (3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响

  • 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
  • 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
  • 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

  (4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集

  (5)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值