1、jdk1.6之前和现在jdk1.8哪些地方做了改动(JVM),为什么要这样做?

PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

jdk1.6中存在永久代(PermGen),JDK 1.8 中实现了从永久代向元空间的转换。

原因:

  • 字符串存在永久代中,容易出现性能问题和内存溢出;
  • 类及方法的信息很难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大容易导致老年代溢出;
  • 永久代会为GC带来不必要的复杂度,并且回收效率极低;
  •  Oracle 可能会将HotSpot 与 JRockit 合二为一。

2、常见的垃圾回收器,说一下CMS或者G1.

Serial、Serial old、ParNew、Parallel Scavenge、Parallel old、CMS、G1

CMS:它是一种以获取最短回收停顿时间为目标的收集器,基于标记-清楚算法实现的,是一种老年代收集器,
通常与ParNew一起使用;
CMS的垃圾收集分为4个过程:初始标记、并发标记、重新标记、并发清除
CMS的优缺点:
优点:以获取最短回收停顿时间为目标的收集器,具有并发收集、停顿时间低的优点;
缺点:对CPU资源非常敏感,收集过程会产生浮动垃圾,标记-清除方式会产生内存碎片。
G1收集器:将新生代和老年代取消了,将堆划分为若干的区域,属于分代收集器,区域的一部分包含新生代,
一部分包含老年代,新生代采用复制算法,老年代采用标记-整理算法。每次根据回收时间来优先回收价值最大的
region(回收所获得的空间大小以及回收所需时间的经验值)
G1垃圾收集器分为4个过程:初始标记、并发标记、最终标记、筛选回收。
G1的特点:并行与并发、分代收集、空间整合(不会产生内存碎片)、可预测的停顿。

3、频繁GC的原因和解决方案

  • 人为原因:例如在代码中调用System#GC或者Runtime#GC方法。
  • 框架原因:在java程序调用相关框架时,框架内部调用了GC方法。
  • 内存原因:当heap大小设置比较小时,会引起频繁的GC,所以在类似于Spark这样对内存性能要求比较高的应用程序运行时,应可能给heap分配较大的内存,这样可以减少频繁的GC现象的发生。

方案即为尽量避免如上做法。

4、高并发后台怎么优化GC

  • CMS+ParNew组合进行垃圾回收(‘压测期间’性能效果更优,可以提高系统吞吐量);
  • -XX:NewRatio=3:设置新生代与老年代的比例(需要根据企业产品的特征来做不同的设置:程序开辟内存驻留时间长短,手动销毁内存等);
  • -XX:MaxTenuringThreshold:设置对象进入老年代的年龄阀值;
  • -XX:SurvivorRatio=8:设置新生代中Eden与Survivor的比例(需要根据企业产品的特征来做不同的设置,一般需要在测试环境做不同配置参数的压力测试,从而得到最佳配置)
  • 设置 -Xms6144M 、-Xmx6144M 、-XX:PermSize=128M 、-XX:MaxPermSize=256M 、-XX:MetaspaceSize(JDK8里面的)等参数(需要在测试环境做不同配置参数的压力测试,从而得到最佳配置)

注释:eg如下(jvm调优)

ava -server -Xms4G -Xmx4G -Xmn2G -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1100 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar c1000k.jar&

这台机器是一个4G内存的机器。

-Xms4G 是指: JVM启动时整个堆(包括年轻代,年老代)的初始化大小。

-Xmx4G 是指: JVM启动时整个堆的最大值。

-Xmn2G是指:年轻代的空间大小,剩下的是年老代的空间。

-XX:SurvivorRatio=1是指:年轻代空间中2个Survivor空间与Eden空间的大小比例。此处为1:1:1,算法如下:比如整个年轻代空间为2G,如果比例为1,那么2/3,则S0/S1/Eden的空间大小是同样的,为666M。

该值不设置,则JDK默认为比例为8,那么是1:1:8,通过上面的算法可以得出S0/S1的大小。我们可以看到官方通过增大Eden区的大小,来减少YGC发生的次数,但有时我们发现,虽然次数减少了,但Eden区满

的时候,由于占用的空间较大,导致释放缓慢,此时stop-the-world的时间较长,因此需要按照程序情况去调优。

-XX:+UseConcMarkSweepGC是指:使用GC的回收类型。这里是CMS类型,JDK1.7以后推荐使用+UseG1GC,被称为G1类型(或Garbage First)的回收器。

我们可以通过jvisualvm.exe中的Visual GC插件查看GC的图形,我们也可以再服务器上执行:jstat -gc 15016 1000,看到每1秒钟java进程号为15016的GC回收情况。

[root@yxdevapp04 c1000k]# jstat -gc 15016 1000

S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT

699008.0 699008.0 29980.4 0.0 699136.0 116881.6 2097152.0 660769.4 21248.0 20071.0 354 54.272 0 0.000 54.272

699008.0 699008.0 29980.4 0.0 699136.0 118344.8 2097152.0 660769.4 21248.0 20071.0 354 54.272 0 0.000 54.272

699008.0 699008.0 29980.4 0.0 699136.0 119895.5 2097152.0 660769.4 21248.0 20071.0 354 54.272 0 0.000 54.272

699008.0 699008.0 29980.4 0.0 699136.0 121383.1 2097152.0 660769.4 21248.0 20071.0 354 54.272 0 0.000 54.272

S0C 是指:Survivor0区的分配空间

S0U 是指:Survivor0区的已经使用的空间

S1C 是指:Survivor1区的分配空间

S1U 是指:Survivor1区的已经使用的空间

EC是指:Eden区所使用的空间

EU是指:Eden区当前使用的空间

OC是指:老年代分配的空间

OU是指:老年代当前使用的空间

PC是指:持久待分配的空间

PU是指:持久待当前使用的空间

YGC是指:年轻代发生的次数,这里是354次

YGCT是指:年轻代发送的总时长,这里是54.272秒,因此每次年轻代发生GC,即平均每次stop-the-world的时长为54.272/354=0.153秒。

FGC是指:年老代回收的次数,或者成为FullGC的次数。

FGCT是指:年老代发生回收的总时长。

GCT是指:包括年轻代YGC及年老代FGC的总时间长。

常结合图形或数据,我们可以看到当EU即将等于EC的时候,此时发生YGC,因此YGC次数+1,YGCT时间增加。

经过实际的调优测试我们发现,当发生YGC的时候,如果S0U或S1U区如果有任意一个区域为0的时候,此时YGC的速度很快,相反如果S0U或者S1U中都有数据,或相对满的时候,此时YGC的时间边长,这就是因为S0/S1及Eden区的比例问题导致的。

经过一定时间的调优,我们基本上可以使得YGC的次数非常少,时间非常快,很长时间,数天都不会发生FGC,此时JVM的调优算是一个好的结果。在MAC电脑上可以通过jconsole调出图形化分析工具。

jvm调优工具:jstack jconsole等。

5、HashSet和HashMap的区别

*HashMap* *HashSet*
HashMap实现了Map接口 HashSet实现了Set接口
HashMap储存键值对 HashSet仅仅存储对象
使用put()方法将元素放入map中 使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值 HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
HashMap比较快,因为是使用唯一的键来获取对象 HashSet较HashMap来说比较慢

6、HashMap中初始化大小为什么是16?

  • 减少hash碰撞
  • 提高map查询效率
  • 分配过小防止频繁扩容
  • 分配过大浪费资源

7、为什么8的时候树化,4(或者其他数)不可以吗?

TreeNodes占用空间是普通Nodes的两倍,所以只有当bin包含足够多的节点时才会转成TreeNodes,而是否足够多就是由TREEIFY_THRESHOLD的值决定的。当bin中节点数变少时,又会转成普通的bin。并且我们查看源码的时候发现,链表长度达到8就转成红黑树,当长度降到6就转成普通bin。

当hashCode离散性很好的时候,树型bin用到的概率非常小,因为数据均匀分布在每个bin中,几乎不会有bin中链表长度会达到阈值。但是在随机hashCode下,离散性可能会变差,然而JDK又不能阻止用户实现这种不好的hash算法,因此就可能导致不均匀的数据分布。不过理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,我们可以看到,一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。

通俗点将就是put进去的key进行计算hashCode时 只要选择计算hash值的算法足够好(hash碰撞率极低),从而遵循泊松分布,使得桶中挂载的bin的数量等于8的概率非常小,从而转换为红黑树的概率也小,反之则概率大。

注:由链表变成红黑树也只是当前桶挂载的bin会进行转换,不会影响其它桶的数据结构

8、HashMap扩容为什么选择红黑树,别的树不可以吗?

  • 红黑树不追求"完全平衡",即不像AVL那样要求节点的 |balFact| <= 1,它只要求部分达到平衡,但是提出了为节点增加颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。
  • 就插入节点导致树失衡的情况,AVL和RB-Tree都是最多两次树旋转来实现复衡rebalance,旋转的量级是O(1)
    删除节点导致失衡,AVL需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN),而RB-Tree最多只需要旋转3次实现复衡,只需O(1),所以说RB-Tree删除节点的rebalance的效率更高,开销更小!
  • AVL的结构相较于RB-Tree更为平衡,插入和删除引起失衡,如2所述,RB-Tree复衡效率更高;当然,由于AVL高度平衡,因此AVL的Search效率更高啦。
  • 针对插入和删除节点导致失衡后的rebalance操作,红黑树能够提供一个比较"便宜"的解决方案,降低开销,是对search,insert ,以及delete效率的折衷,总体来说,RB-Tree的统计性能高于AVL.
  • 故引入RB-Tree是功能、性能、空间开销的折中结果
    *AVL更平衡,结构上更加直观,时间效能针对读取而言更高;维护稍慢,空间开销较大。
    *红黑树,读取略逊于AVL,维护强于AVL,空间开销与AVL类似,内容极多时略优于AVL,维护优于AVL。

    基本上主要的几种平衡树看来,红黑树有着良好的稳定性和完整的功能,性能表现也很不错,综合实力强