三、JVM面试题:
jvm模块划分的详细知识可以看:Java虚拟机内存——栈、堆、Non-heap_liumei90-CSDN博客_jvm nonheap 个人建议这块内容在理解的基础上进行记忆
Java的另一个重要特点是,在JVM启动之后,它能够加载编译的Java类(字节码)。根据程序的大小,在刚刚重启之后,程序在类加载过程中性能会显著降低。这种现象是因为内部JIT编译器在重启之后需要重新开始优化。
自JDK 1.7版本之后,有一些改进值得大家重视。例如默认的JDK class loader具有更好的装在类并发能力。
gc的种类
Minor GC :从年轻代空间(包括 Eden 和 Survivor 区域)回收内存
Major GC: 是清理永久代。
Full GC :是清理整个堆空间—包括年轻代和永久代。
JVM的参数设置
-Xmx和-Xms用来设置Java堆内存的初始大小和最大值。依据个人经验这个值的比例最好是1:1或者1:1.5。比如,你可以将-Xmx和-Xms都设为1GB,或者-Xmx和-Xms设为1.2GB和1.8GB。
-Xmn 决定了新生代空间的大小,-XX:SurvivorRatio 指定伊甸园区(Eden)与幸存区大小比例默认是8.(假如值为 4 表示:Eden:S0:S1 = 4:3:3 )
-XX:PermSize、-XX:MaxPermSize 用来控制方法区的大小,通常设置为相同的值。
Java GC机制
Java中对象都在堆上创建。为了GC,堆内存分为三个部分,也可以说三代,分别称为新生代,老年代和永久代。其中新生代又进一步分为Eden区,Survivor 1区和Survivor 2区(如下图)。新创建的对象会分配在Eden区,在经历一次Minor GC后会被移到Survivor 1区,再经历一次Minor GC后会被移到Survivor 2区,直到升至老年代,需要注意的是,一些大对象(长字符串或数组)可能会直接存放到老年代。
什么时候开始GC
当新生代满了会进行Minor GC,升到老年代的对象大于老年代剩余空间时会进行Major GC
GC做什么
新生代采用复制算法,老年代采用标记-清除或标记-整理算法
GC Root是个什么东西呢?
每个应用程序都包含一组根(root)。每个根都是一个存储位置,其中包含指向引用类型对象的一个指针。该指针要么引用托管堆中的一个对象,要么为null。
在应用程序中,只要某对象变得不可达,也就是没有根(root)引用该对象,这个对象就会成为垃圾回收器的目标。注意,只有引用类型的变量才被认为是根,值类型的变量永远不被认为是根
什么是java内存模型
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示,和上图很类似。
备注:面试前应做充分整备,过后及时总结不足,不同公司面试的侧重点可能不同,准备的重点也要不同
四、Java调优
具体参考http://www.importnew.com/22336.html
在系统层面能够影响应用性能的一般包括三个因素:CPU、内存和IO,可以从这三方面进行程序的性能瓶颈分析。
CPU分析:
找出占用cpu最高的线程
1,使用命令top -p ,显示你的java进程的内存情况,pid是你的java进程号,可以用jdk自带的jsp命令获取
2,按H,获取每个线程的内存情况
3,找到内存和cpu占用最高的线程pid,比如15248
4,执行 printf 0x%x 15248 得到 0x3b90 ,此为线程id的十六进制
5,执行 jstack 123|grep -A 10 3b90,得到线程堆栈信息中3b90这个线程所在行的后面10行
6,查看对应的堆栈信息找出可能存在问题的代码
也可以使用jstat来查看对应进程的gc信息,以判断是否是gc造成了cpu繁忙。
还可以通过vmstat,通过观察内核状态的上下文切换(cs)次数,来判断是否是上下文切换造成的cpu繁忙。
内存分析:排查堆内存问题的常用工具是jmap,是jdk自带的
IO分析:文件IO可以使用系统工具pidstat、iostat、vmstat来查看io的状况。网络IO查看网络io状况,一般使用的是netstat工具。可以查看所有连接的状况、数目、端口信息等
五、系统架构:
5.1、秒杀问题
1、秒杀未开始前,前端统一调用一个秒杀地址暴露接口,判断是否可以调用真正的秒杀地址接口,秒杀地址暴露接口,伴有大量的并发请求秒杀数据的操作,可以放入redis
如果从redis中获得数据,是获得byte的数组,而不是具体的类,因为redis无法帮你反序列化,因此,反序列化的操作应该由你自己完成。protostuff的反序列化效率比Serializable高了很多倍,而且压缩后的空间比Serializable小了10倍左右
2、真正的秒杀接口地址增加md5加密部分,不同的商品的秒杀地址不同,避免了用户利用插件或者猜测拼接url等其他方法,在活动未开始之前,访问秒杀地址。
3、判用户的余额是否可以秒杀该商品,可以的话发条消息,告诉消费者可以进行秒杀
4、在消费者里处理所有的秒杀请求
5、库存为零后,不再消费中间的消息。转去结束画面。
6、将事务SQL在Mysql端进行(存储过程),减少网络延迟和GC带来的时间消耗
7、服务器负载最小化。比如非逻辑处理等一律移去前台,尽可能静态化页面等。
详细代码:
https://gitee.com/lzhcode/maven-parent/blob/master/lzh-seckill
5.2、如何实现分布式session
目前用户的认证多半是基于表单的认证,基于表单的认证一般会使用 Cookie 来管理Session
用户在登录的时候,会在Web服务器中开辟一段内存空间Session用于保存用户的认证信息和其他信息,用户登录成功之后会通过Set-Cookie的首部字段信息,通知客户端保存Cookie,而这Cookie保存的就是服务器端Session的ID,下次请求的时候客户端会带上该Cookie向服务器端发送请求,服务器端进行校验,如果Session中保存的有该ID的Session就表示用户认证通过,否则失败!
但是一个显著的问题就是,在集群模式下如果通过Nginx负载均衡的时候,如果有一个用户登录的时候请求被分配到服务器A上,登录成功后设置的Session就会存放在服务器A上了,但是在服务器B上却没有该用户的Session数据,当用户再次发起一个请求的时候,此时请求如果被分配到服务器B上,则就不会查询到该用户的登录状态,就会出现登录失败的情况!
一种可以想到的方式就是将多个Web服务器上存储的Session统一存储到某一存储介质中,保证进集群中的每一台机器都可以看到所有相同Session数据,这里的同步体现在所有的Session存储在同一的存储介质里边。
幸运的是我们常用的Tomcat容器已经为我们提供了一个接口,可以让我们实现将Session存储到除当前服务器之外的其他存储介质上,例如Redis等。
Spring Session的本质就是通过实现Tomcat提供的该接口将Session存储到Redis中,以此来实现Session的统一存储管理
下面的代码演示了如何基于cookie实现分布式session
@Service public class MiaoshaUserService { public static final String COOKI_NAME_TOKEN = "token"; @Autowired MiaoshaUserDao miaoshaUserDao; @Autowired RedisService redisService; public MiaoshaUser getById(long id) { return miaoshaUserDao.getById(id); } public MiaoshaUser getByToken(HttpServletResponse response, String token) { if(StringUtils.isEmpty(token)) { return null; } MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class); //延长有效期 if(user != null) { addCookie(response, token, user); } return user; } public boolean login(HttpServletResponse response, LoginVo loginVo) { if(loginVo == null) { throw new GlobalException(CodeMsg.SERVER_ERROR); } String mobile = loginVo.getMobile(); String formPass = loginVo.getPassword(); //判断手机号是否存在 MiaoshaUser user = getById(Long.parseLong(mobile)); if(user == null) { throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST); } //验证密码 String dbPass = user.getPassword(); String saltDB = user.getSalt(); String calcPass = MD5Util.formPassToDBPass(formPass, saltDB); if(!calcPass.equals(dbPass)) { throw new GlobalException(CodeMsg.PASSWORD_ERROR); } //生成cookie String token = UUIDUtil.uuid(); addCookie(response, token, user); return true; } private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) { redisService.set(MiaoshaUserKey.token, token, user); Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token); cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds()); cookie.setPath("/"); response.addCookie(cookie); } }
根据请求的参数类型进行拦截
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebConfig extends WebMvcConfigurerAdapter{ @Autowired UserArgumentResolver userArgumentResolver; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(userArgumentResolver); } }
import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Service; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import com.imooc.miaosha.domain.MiaoshaUser; import com.imooc.miaosha.service.MiaoshaUserService; @Service public class UserArgumentResolver implements HandlerMethodArgumentResolver { @Autowired MiaoshaUserService userService; public boolean supportsParameter(MethodParameter parameter) { //有参数是MiaoshaUser类型时执行resolveArgument方法 Class<?> clazz = parameter.getParameterType(); return clazz==MiaoshaUser.class; } public Object (MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN); String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN); if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) { return null; } String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken; return userService.getByToken(response, token); } private String getCookieValue(HttpServletRequest request, String cookiName) { Cookie[] cookies = request.getCookies(); for(Cookie cookie : cookies) { if(cookie.getName().equals(cookiName)) { return cookie.getValue(); } } return null; } }
请求入口
@Controller @RequestMapping("/goods") public class GoodsController { @RequestMapping("/to_list") public String list(Model model,MiaoshaUser user) { model.addAttribute("user", user); return "goods_list"; } }
六、tomcat调优:
1.调整tomcat的占用内存 linux 下vi catalina.sh 查找到tomcat内存参数一行:/ JAVA_OPTS,如果找不到则在第一行写上 将JAVA_OPTS="-Xms 1024m –Xmx 1520m"一行的两个参数依据服务器实际内存数量分别进行更改: - Xms为tomcat启动初始内存,一般为服务器开机后可用空闲内存减去100M - Xmx为tomcat最大占用内存,一般为服务器开机后可用空闲内存减去50M 一般说来,您应该使用物理内存的 80% 作为堆大小。 2.在tomcat配置文件server.xml中的<Connector />配置中,和连接数相关的参数有: maxThreads="150" 表示最多同时处理150个连接,Tomcat使用线程来处理接收的每个请求。这个值表示 Tomcat可创建的最大的线程数。默认值200。 minSpareThreads="25" 表示即使没有人使用也开这么多空线程等待 maxSpareThreads="75" 表示如果最多可以空75个线程,例如某时刻有80人访问,之后没有人访问了,则 tomcat不会保留80个空线程,而是关闭5个空的。 (一旦创建的线程超过这个值,Tomcat就会关闭不再需要 的socket线程。默认值50。 ) acceptCount="100" 当同时连接的人数达到maxThreads时,还可以接收排队的连接数量,超过这个连接的 则直接返回拒绝连接。(指定当任何能够使用的处理请求的线程数都被使用时,能够放到处理队列中的请求数, 超过这个数的请求将不予处理。默认值100。 ) 其中和最大连接数相关的参数为maxThreads和acceptCount。如果要加大并发连接数,应同时加大这两个参 数。 web server允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000 个左右。tomcat5中的配置示例: <Connector port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" acceptCount="100"/> 主要是调整maxThreads 和acceptCount的值 对于其他端口的侦听配置,以此类推。 在tomcat配置文档server.xml中的配置中,和连接数相关的其他参数有: enableLookups: 是否反查域名,默认值为true。为了提高处理能力,应配置为false connnectionTimeout: 网络连接超时,默认值60000,单位:毫秒。配置为0表示永不超时,这样配置有隐患的。通常可配置为30000毫秒。 maxKeepAliveRequests: nginx动态的转给tomcat,nginx是不能keepalive的,而tomcat端默认开启了keepalive,会等待keepalive 的timeout,默认不设置就是使用connectionTimeout。 所以必须设置tomcat的超时时间,并关闭tomcat的keepalive。否则会产生大量tomcat的socket timewait。 maxKeepAliveRequests="1"就可以避免tomcat产生大量的TIME_WAIT连接,从而从一定程度上避免tomcat假 死。 试试设置tomcat/conf/server.xml: maxKeepAliveRequests="1" connectionTimeout="20000" maxKeepAliveRequests="1"表示每个连接只响应一次就关闭,这样就不会等待timeout了。 <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="30000" maxKeepAliveRequests="1" redirectPort="8443" bufferSize="8192" sockedBuffer="65536" acceptCount="200"/>