Java中高级面试题总览(三)(2)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Java中高级面试题总览(三)

三、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"/>
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
JavaScript 前端开发 Java
【JAVA面试题】什么是引用传递?什么是值传递?
【JAVA面试题】什么是引用传递?什么是值传递?
|
3天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
3天前
|
Java
[Java 面试题] ArrayList篇
[Java 面试题] ArrayList篇
|
4天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
32 1
|
4天前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
15 0
|
4天前
|
Java 编译器
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
15 0
|
SQL 缓存 安全
Java高频面试题目
面试时面试官最常问的问题总结归纳!
101 0
JAVA高频面试题目集锦(6)
JAVA高频面试题目集锦(6)
104 0
JAVA高频面试题目集锦(6)
|
存储 安全 Java
JAVA高频面试题目集锦(5)
JAVA高频面试题目集锦(5)
148 0
JAVA高频面试题目集锦(5)
|
算法 安全 Java
JAVA高频面试题目集锦(4)
JAVA高频面试题目集锦(4)
82 0
JAVA高频面试题目集锦(4)