그 몇 년 동안 내가 밟은 구덩이 중 몇 개를 밟았습니까?

함께 만들고 함께 성장하기 위해 함께 노력하십시오! "너겟 데일리 뉴플랜·8월 업데이트 챌린지" 참여 30일차입니다. 클릭하시면 이벤트 내용을 보실 수 있습니다.

배경

나는 수년간 소프트웨어 업계에 있었고 생산 및 개발에서 관련 구덩이를 밟았습니다. 오늘 당신과 그것을 공유 할 것입니다. 관련 경험을 축적하고 반복되는 함정을 피하시기 바랍니다.

정수 오토박싱 및 언박싱

예를 들어

 public static void main(String[] args) 
    {
        Integer x1 = 10;
        Integer x2 = 10;
        boolean r = x1==x2;
        System.out.println("x1==x2:" + r);

        Integer y1 = 300;
        Integer y2 = 300;
        boolean r1 = y1==y2;
        System.out.println("y1==y2:" + r1);

        Integer m1 = new Integer(300);
        Integer m2 = new Integer(300);
        boolean r2 = m1==m2;
        System.out.println("m1==m2:" + r2);
        
        Integer n1 = new Integer(20);
        Integer n2 = new Integer(20);
        boolean r3 = n1.intValue()==n2.intValue();
        System.out.println("n1==n2:" + r3);
   }
复制代码

설명: Integer unboxing 및 boxing 문제와 관련하여 Integer의 기본 캐시 범위만 마스터하면 이러한 질문에 쉽게 답변할 수 있습니다. Integer는 int 타입의 wrapper 클래스로, int 값이 Integer에 할당되면 valueOf(int i) 메서드를 사용하여 자동으로 boxing한다. 기본적으로 cache[] 캐시 범위는 [-128,127],

문자열의 == 및 euals 문제

String's == and equal의 질문에 대해, 그 해 인턴십 면접에서 첫 번째 질문은 이것이었는데, 결과는 틀리고 면접관은 나가서 우회전하라고 했습니다. 그래서 아직도 기억이 생생하다.

예를 들어

 public static void main(String[] args)
    {
        //
        String s1 = "abc";
        String s2 = "abc";
        System.out.println("s1 == s2 : " + (s1 == s2));
        
        String s3 = new String("abc");
        String s4 = new String("abc");
        System.out.println("s3 == s4 : " + (s3 == s4));
        
        String s5 = "ab" + "cd";
        String s6 = "abcd";
        System.out.println("s5 = s5 : " + (s5 == s6));
        
        String str1 = "ab"; 
        String str2 = "cd";
        String str3 = str1 + str2;
        String str4 = "abcd";
        System.out.println("str4 = str3 : " + (str3 == str4));
        
        String str6 = "b";
        String str7 = "a" + str6;
        String str8 = "ab";
        System.out.println("str7 = str8 : " + (str7 == str8));
        
        //常量话
        final String str9 = "b";
        String str10 = "a" + str9;
        String str11 = "ab";
        System.out.println("str9 = str89 : " + (str10 == str11));
        
        String s12="abc";
        String s13 = new String("abc");
        System.out.println("s12 = s13 : " + (s12 == s13.intern()));
    }
复制代码

참고: String의 == 및 equal과 관련하여 String의 메모리 모델을 마스터해야 위의 질문에 정확하게 답할 수 있고 구덩이를 밟는 것을 두려워하지 않습니다.

정밀도 손실 문제

정밀도 손실 문제는 다음 예와 같이 주로 부동 소수점 유형 및 BigDecimal 데이터에 반영됩니다.

public class BigDecimalTest
{
   public static void main(String[] args)
    {
          float a = 1;
          float b = 0.9f;
          System.out.println(a - b);
    }
}
复制代码

0.1의 이진 표현이 무한 루프이기 때문에 출력은 0.100000024입니다. 컴퓨터의 자원이 한정되어 있기 때문에 0.1을 2진수로 정확하게 표현할 방법이 없고 "근사값"으로만 표현할 수 있습니다. 즉, 제한된 정밀도의 경우 0.1에 가까운 2진수를 최대화하면 결과적으로 정밀도가 떨어집니다.

따라서 BigDecimal 유형을 사용하여 작동할 수 있지만 BigDecimal에도 정밀도 손실 문제가 있습니다.

BigDecimal c = new BigDecimal(1.0);
          BigDecimal d =new BigDecimal(3.0);
          BigDecimal e =c.divide(d);
          System.out.println("e = " + e);
复制代码

프로그램을 실행하면 다음 오류가 보고됩니다.

사진.png

이는 나눗셈 중 몫이 무한소수점(0.333...)이고 연산 결과가 정확한 숫자일 것으로 예상되는 경우 ArithmeticException이 발생하므로 BigDecimal이 해당 곱셈을 수행하고 나눗셈 시 소수점 이하 자릿수를 유지하십시오. 위의 코드는 다음과 같이 수정할 수 있습니다.

   BigDecimal c = new BigDecimal(1.0);
          BigDecimal d =new BigDecimal(3.0);
          BigDecimal e =c.divide(d,2,RoundingMode.HALF_UP);
          System.out.println("e = " + e);
复制代码

값에 의한 전달 및 참조에 의한 전달

예시 설명:

 public static void func(int a)
 {
    a=30;
 }
public static void main(String[] args) 
{
  int a=20;
  func(a);
  System.out.println(a);
   
   String c="abc";
   Strinfunc(c);
   System.out.println(c);
}

public static void Strinfunc(String c)
{
   c="c";
}
复制代码

설명: pass-by-value와 pass-by-reference를 이해해야 위의 예에 대한 답을 쉽게 출력할 수 있습니다.

值传递:是指在调用函数时,将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,就不会影响到实际参数。

引用传递:是指在调用函数时,将实际参数的地址传递到函数中,那么在函数中对参数进行修改,将会影响到实际参数。

List.subList内存泄露

实例说明:

  public static void main(String[] args)
    {
        List cache = new ArrayList();
        try
        {

            while (true)
            {

                List list = new ArrayList();

                for (int j = 0; j < 100000; j++)
                {
                    list.add(j);
                }

                List sublist = list.subList(0, 1);
                cache.add(sublist);
            }
        }
        finally
        {
            System.out.println("cache size = " + cache.size());

        }
    }
复制代码

说明:这是因为SubList的实例中,保存有原有list对象的强引用,只要sublist没有被jvm回收,那么这个原有list对象就不能gc,即使这个list和其包含的对象已经没有其他任何引用。

for循环中删除元素报错

示例:

  public static void main(String[] args) {
          String test = "1,2,3,4,5,6,7";
          List<String> testList = Arrays.asList(test.split(","));
          for(int i = 0; i < testList.size(); i++){
              String temp = testList.get(i);
                  testList.remove(temp);
          }
      }
复制代码

运行上述的代码报如下错误:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.remove(AbstractList.java:161)
	at java.util.AbstractList$Itr.remove(AbstractList.java:374)
	at java.util.AbstractCollection.remove(AbstractCollection.java:293)
	at com.skywares.fw.juc.integer.ListTest.main(ListTest.java:14)

复制代码

这是因为fail-fast,即快速失败,它是Java集合的一种错误检测机制。当多个线程对集合(非fail-safe的集合类)进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException(当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常)。

解决的办法

可以通过迭代器来进行删除、或者采用java8的filter过滤 采用java8的filter来过滤

testList.stream().filter(t -> !t.equals("a")).collect(Collectors.toList());
          System.out.println(testList);
复制代码

SimpleDateformat格式化时间

SimpleDateFormat是大家比较常用的,而且在一般情况下,一个应用中模式都是一样的,所以很多人都喜欢使用如下的方式定义SimpleDateFormat

public class SimpleDateFormatTest
{
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args)
    {
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(simpleDateFormat.format(Calendar.getInstance()
                .getTime()));
    }
}
复制代码

采用这样的定义方式,如果在多线程的情况下,存在很大的安全隐患。

public class SimpleDateFormatTest
{
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss");

    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1,
            TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));
    
    public static void main(String[] args)
    {
        SimpleDateFormatTest simpleDateFormatTest =new SimpleDateFormatTest();
        simpleDateFormatTest.test();
    }

    public void test()
    {
        while (true)
        {
            poolExecutor.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    String dateString = simpleDateFormat.format(new Date());
                    try
                    {
                        Date parseDate = simpleDateFormat.parse(dateString);
                        String dateString2 = simpleDateFormat.format(parseDate);
                        System.out.println(dateString.equals(dateString2));
                    }
                    catch (ParseException e)
                    {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
复制代码

运行代码如果出现了false,则说明SimpleDateFormat在多线程不安全,导致结果错误,那么我们如何避免呢?可以采用java8提供的DateTimeFormatter来解决SimpleDateFormat在多线程的不安全问题。

未释放对应的资源,导致内存溢出

示例

ZipFile zipFile = new ZipFile(fileName);
Enumeration<ZipEntry> elments = (Enumeration<ZipEntry>) zipFile.getEntries();
while (elments.hasMoreElements())
{
	System.out.println(elments.nextElement());
}
复制代码

说明:数据库资源,文件资源,Socket资源以及流资源等,这些资源都是有限的,当程序中的代码申请了资源之后,却没有释放,就会导致资源没有被释放,当系统没有充足的资源时,就会导致系统因为资源枯竭而导致服务不可用。

ThreadLocal内存泄漏

ThreadLocal은 인터뷰에서 자주 묻는 질문이기도 하지만 사용 중 개체를 수동으로 해제해야 합니다. 그렇지 않으면 메모리 누수가 보고됩니다.

일반적인 메모리 누수의 예

static class UserInfoContext{
    public static ThreadLocal<UserInfo> userLocal =new ThreadLocal<>();
}

@WebFilter("/*")
public class UserInfoFilter implements Filter{

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        if(hasLogined(req)){
            UserContext.userLocal.set(extractUserInfo(req));
        }
        chain.doFilter(req, res);
    }

}
复制代码

예시:

App 애플리케이션이 Tomcat에 의해 로드되고 실행될 때 요청이 UserInfoFilter를 통과할 때 실행 스레드의 private Map에 항목이 삽입되고 이 항목은 UserInfo 개체에 대한 강력한 참조를 갖습니다. , 비록 Tomcat이 서블릿/필터 개체에 대한 참조를 릴리스했지만 스레드의 개인 ThreadLocalMap에는 여전히 UserInfo 개체에 대한 참조가 있고 UserInfo 개체에 연결할 수 있으므로 UserInfo 클래스에도 연결할 수 있습니다. Tomcat이 클래스 격리를 수행했기 때문입니다. , 다음에 앱이 로드될 때 동일한 클래스가 계속 로드됩니다. 다시 로드하십시오. Tomcat에서 앱이 로드 및 언로드를 반복하면서 JVMPermGen의 클래스가 점점 더 누적되어 결국 java.lang.OutOfMemoryError가 발생합니다. : PermGen 공간.

요약하다

이 기사는 몇 가지 일반적인 피트 스테핑 기록을 나열합니다. 경험을 축적하고 반복 피트 스테핑을 피할 수 있기를 바랍니다. 질문이 있으면 언제든지 피드백을 제공하십시오.

추천

출처juejin.im/post/7136442438389858311