应用分层的百花齐放,导致对于分层与领域模型的理解多样化,非常不利于团队合作。本章主要说明应用工程分层思想、二方库约定及基本的服务器知识。而老四本篇所要浅析的就是第六章的第三篇服务器的几点知识。不知道为什么孤尽会在这一点只写了5点,虽然说这5点很能够说明问题,既包括服务器调优也说明jvm调优需要注意的地方,但是我个人觉得其实在服务器以及jvm调优上面需要注意的地方是在太多。老四先把这几点浅析一下,以后如果在遇到这方面的问题以及经验会不断的丰富这篇文章,请诸君持续关注。
1.[推荐] 高并发服务器建议调小TCP协议的time_wait超时时间。
说明:操作系统默认240s后,才会关闭处于time_wait状态的连接。在高并发访问下,服务器端会因为处于time_wait的连接数过多,而无法建立新的连接,所以需要在服务器上调小此等待值。
正例:在Linux服务器上可通过变更/etc/sysctl.conf文件去修改该默认值(单位:s)。
net.ipv4.tcp_fin_timeout = 30
老四附言:
关于tcp协议的解释:
传输控制协议(Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据包协议(UDP)是同一层内另一个重要的传输协议。
在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元(MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。更多信息请参考维基百科的介绍: tcp
关于网络分层简单的描述一下:
— TCP/IP协议族: 基于某种相同的规则、方法进行通信(FTP HTTP TCP IP UDP)
— TCP/IP的分层管理:
-
应用层:(FTP DNS) — 决定了向用户提供应用服务时通信的活动
-
传输层:(TCP UDP) — 提供处于网络连接中的两台计算机之间的数据传输
-
网络层 — 处理在网络上流动的数据包(最小数据单位)
-
IP协议:
IP地址 –指明了节点被分配到的地址(可变)MAC地址 –网卡所属的固定地址(不变) -
链路层 — 处理连接网络硬件的部分
— TCP/IP通信传输流:
-
发送端从应用层往下走,接收端则往应用层上走
-
发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该层所属的首部信息.反之,接收端在层与层之间传输数据时,每经过一层时会对应的把首部去掉
- TCP协议:面向连接、可靠的流协议。
- UDP协议:不具有可靠性的数据报协议。只确保发送消息,其他处理都由上层应用来完成。
- TCP应用场景:效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。
- UDP应用场景:效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。
-
为了防止已失效的链接请求报文段突然又传送到了服务端,因而产生错误
-
解决网络中存在延迟的重复分组
-
因为tcp是全双工模式,接收到FIN时意味将没有数据再发来,但是还是可以继续发送数据
关于为什么要减少等待时间,说明解答的已经很详细了,不过在第二点我会继续补充一些服务器关于高并发优化更多的一些东西。
2.[推荐] 调大服务器所支持的最大文件句柄数(File Descriptor,简写为fd)。
说明:主流操作系统的设计是将TCP/UDP连接采用与文件一样的方式管理,即一个连接对应于一个fd。主流的Linux服务器默认支持最大的fd数量为1024,当并发连接数很大时很容易因为fd不足而出现”open too many files”错误,导致新的连接无法建立。建议将Linux服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。
老四附言:
这里需要注意一点,如果你单纯的使用类似”ulimit -n 2048″这样的命令来修改的最大文件句柄数是只对当前进程有效的,当你在开启一个终端或者进程的时候,句柄数依然是1024,所以当你需要全局修改的时候,需要修改/etc/security/limits.conf文件,修改之后重新执行ulimit -n。
3.[推荐] 给JVM设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM(Out of Memory,内存溢出)场景时输出dump信息。
说明:OOM的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错非常有价值。
Java中的OOM异常基本分为4类:
- Java堆溢出
- 虚拟机栈和本地方法栈溢出
- 方法区和运行时常量池溢出
- 本机直接内存溢出
Java堆溢出
Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.glorze.jvm; import java.util.ArrayList; import java.util.List; /** * Java堆溢出 * @ClassName HeapOom * @author: glorze.com * @since: 2018年4月12日 下午1:59:34 */ public class HeapOom { static class OomObject { } public static void main(String[] args) { List<OomObject> ooList = new ArrayList<OomObject>(); while (true) { ooList.add(new OomObject()); } } } |
解决Java堆溢出的常规办法:
- 如果是内存泄露可以通过工具查看泄露对象到GC Roots的引用链。定位泄露代码的位置。
- 如果不是内存泄漏,对象需要存活,则检查虚拟机的堆参数(-Xmx和-Xms),检查对象的生命周期。
虚拟机栈和本地方法栈溢出
关于虚拟机栈和本地方法栈,在Java虚拟机(hotspot虚拟机)规范中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOvertiowError异常。
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryErro:异常。
方法区和运行时常量池溢出
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 31 |
package com.glorze.jvm; import java.util.ArrayList; import java.util.List; /** * 运行时常量池溢出 * @ClassName RuntimeConstantPoolOom * @author: glorze.com * @since: 2018年4月12日 下午3:55:43 */ public class RuntimeConstantPoolOom { /** * 关于String.intern()的简单说明: * String.intern()是一个native方法,作用是如果字符串常量池中已经包含一个等于此String对象的字符串,返回代表池中这个字符串的String对象; * 否则,将此String对象包含的字符串添加到常量池并返回此String对象的引用。 * @Title: main * @param args * @return void * @author: 高老四博客 * @since: 2018年4月12日 下午3:57:18 */ public static void main(String[] args) { // 使用List保持着常量池引用,避免Full GC回收常量池行为 List<String> strList = new ArrayList<String>(); int i = 0; while (true) { strList.add(String.valueOf(i++).intern()); } } } |
本机直接内存溢出
由DirectMemory(直接内存)导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常信息,排错的过程中如果发现dunp文件较小异或代码中使用了NIO,可以考虑检查这方面的原因。
4.[推荐] 在线上生产环境,JVM的Xms和Xmx设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。
没啥好说的,列一个常见的JVM参数含义列表:
参数名称 | 含义 | 默认值 | |
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小(1.4or lator) | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 |
|
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | ||
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | ||
-XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 | |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | |
-Xss | 每个线程的堆栈大小 | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长) 和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:”” -Xss is translated in a VM flag named ThreadStackSize” 一般设置这个值就可以了。 |
|
–XX:ThreadStackSize | Thread Stack Size | (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.] | |
-XX:NewRatio | 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。 |
|
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 | |
-XX:LargePageSizeInBytes | 内存页的大小不可设置过大, 会影响Perm的大小 | =128m | |
-XX:+UseFastAccessorMethods | 原始类型的快速优化 | ||
-XX:+DisableExplicitGC | 关闭System.gc() | 这个参数需要严格的测试 | |
-XX:MaxTenuringThreshold | 垃圾最大年龄 | 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效. |
|
-XX:+AggressiveOpts | 加快编译 | ||
-XX:+UseBiasedLocking | 锁机制的性能改善 | ||
-Xnoclassgc | 禁用垃圾回收 | ||
-XX:SoftRefLRUPolicyMSPerMB | 每兆堆空闲空间中SoftReference的存活时间 | 1s | softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap |
-XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 0 | 单位字节 新生代采用Parallel Scavenge GC时无效 另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象. |
-XX:TLABWasteTargetPercent | TLAB占eden区的百分比 | 1% | |
-XX:+CollectGen0First | FullGC时是否先YGC | false |
5.[参考] 服务器内部重定向使用 forward;外部重定向地址使用URL拼装工具类来生成,否则会带来 URL 维护不一致的问题和潜在的安全风险。
老四附言:
服务器内部使用转发行为效率较高。再来复习一下forward与重定向的区别吧。
本质上,转发是服务器行为,而重定向是客户端行为。在重定向的时候其实发生了两次request,实际为request-response-request,所以如果你在面试的时候已经答出了下图的答案之后把这几句话加上,可能会加点分。
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请赞助盒烟钱,点我去赞助。抱拳。