2023-01-31问题记录(三个问题)

序言

在我们的开发过程中,很多问题不起眼,可是致命,而且测试很难测出来,因为引发的场景不同,不可能做到一一测试,今天记录一下遇到的三个致命小问题。

关闭资源的最优方式

从以往来看,try-finally 语句是保证资源正确关闭的最佳方式,即使是在程序抛出异常或返回的情况下,看一段代码:

BufferedReader br = new BufferedReader(new FileReader(fileRegPath));
try {
return br.readLine();
} finally {
br.close();
}

这代码看起来没什么问题,甚至你以及我之前觉得天衣无缝。
我们连接的物理机发生了异常(我不知道这个事情当时),于是后面的异常把前面的给冲了。我查看堆栈信息,竟完全看不到我想看到的东西。导致我的工作停滞了一会。最后我才知是物理机故障了。如果引入第二个资源,那才是灾难。看看copy的代码。

try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
} finally {
in.close();
}

随后我思考解决方案,幸运的是,我找到了答案。在Java 7之后引入了 try-with-resources 语句时,问题就解决了。要使
用这个构造,资源必须实现 AutoCloseable 接口,该接口由一个返回为 void 的 close 组成。Java类库和第三方类库中的许多类和接口现在都实现或继承了 AutoCloseable 接口。如果你编写的类表示必须关闭的资源,那么这个类也应该实现 AutoCloseable 接口。


try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}

后一个异常被抑制,前面的能看到了,当然我们可以查看被抑制的异常,你可以参考这一段代码:

  try{
      somethingWrong("");
  }catch(Exception e){
      e.printStackTrace();
      for(Throwable t : e.getSuppressed()){
          t.printStackTrace();
  }

copy的问题也解决了

static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}

借用书上的一句话:
结论很明确:在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。 生成的代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources 语句在编写必须关闭资源的代码时会更容易,也不会出错,而使用 try-finally 语句实际上是不可能的。

尽量不要使用数组

因为阅读了不少源码。例如spring,mybatis,tubalu,shiro,juc等。我对他们使用数据的实力叹为观止。所以尝试效仿这些大神们。但是弄巧成拙,经常出问题,而且问题也是不容易测试出来的,这让我对大神们更多了几分敬意。看我写的代码(已简化):

public class NodeQueue<T> {
    private final T[] array;

    public NodeQueue(Collection<T> array) {
        this.array = (T[]) array.toArray();
    }

    public T[] getArray() {
        return array;
    }

    public static void main(String[] args) {
        List<List<String>> lists = new ArrayList<>();
        NodeQueue<List<String>> nodeQueue = new NodeQueue<>(lists);
        
    }
    
    // do others ...
}

这样的代码一运行就报错,但是你作为提供者,你根本不知道客户端会怎么玩,所以出错了不细心找一下都蒙蔽的,但是如果使用List则没有问题,所以不能盲目效仿。必须要保证代码的健壮性。

大集合保存的时候莫名其妙OOM

这个问题我当时找了好久。我们其实很多时候都有这样的代码

Object result = cells[size--];
// do save

这代码看起来是没有任何问题的,当我的batonce(代码见batonce源码,库站搜索batonce,可以下载到包,里面这样的问题都在v1.0.1中修复,可惜的是近期不打算发布。)运行了几个小时之后,莫名其妙的OOM了。我觉得很惊讶,在解析过程中我尽量保证内存最小,我用jdk自带的工具证明了这个猜想。但是内存还是在测试环境溢出了,我修改代码也无济于事,后面我苦苦的找寻可能存在有问题的代码,一无所获,直到另外一个已经离职同事的代码引发了我的注意,只是引用,没有任何攻击性和嘲讽性。

List<Object> objects = searchData();
// do others

我看到这个代码立刻想到了我对单元格的处理,一个pdf中可能有很多单元,一次处理几十个Pdf,所以我怀疑list没有被gc掉,可是他的引用已经过期了。所以我变更了代码:

Object result = cells[size--];
 cells[size] = null;
// do save

果然问题迎刃而解!这其实是一个内存泄漏的事故,其实你可以去测试,越详细越好,都不会发现这个问题,因为这个现象很难复现,即使没有办法复现,他内存慢慢积累,然后我们的那个gc活动就会频繁,频繁了就会性能下降,因为gc太多了,时间被gc占了一大部分,是个灾难。反正有不用的东西,释放掉就对了。

结束语

当一个再优秀的程序员,有时也会难免犯这些低级的错误,有时不自觉也会犯错,犯错误容易,但是修的时候非常麻烦,所以要约束自己,不要肆意妄为,就算当时不爆炸,日后总有爆炸的一天,那时候会有种 被水淹没 不知所措的感觉!所以,在能力范围内把关好每一行代码。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值