java查缺补漏

最近有个需求要把项目发布在weblogic12c服务器中,按照网上的教程对springboot项目进行改造后,陆续遇到了几个问题:
1.weblogic报错:afu.com.sun.source.tree.Tree$Kind?

这个错误 把项目中用到的依赖guava版本更换为24.1-jre解决

2.controller层返回数据的时候,报Jackson转换的错误,nosuchMethod 之类的,

把spring-boot-starter-web中的Jackson-core排除掉,然后把json解析换成了fastJson解决。

3.Java将图片转化为二进制字符格式而不是二进制流,不转成bitmap数组格式的?

import java.awt.image.BufferedImage; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.File; 
import java.io.IOException;
import javax.imageio.ImageIO;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class TestImageBinary {
static BASE64Encoder encoder = new sun.misc.BASE64Encoder();   
static BASE64Decoder decoder = new sun.misc.BASE64Decoder();   
   
public static void main(String[] args) {   
    System.out.println(getImageBinary());   
       
    base64StringToImage(getImageBinary());   
}   
   
static String getImageBinary(){   
    File f = new File("f://123456.jpg");    //这里gif动态图不可以,虽然在后面也能输出gif格式,但是却不是动图
    BufferedImage bi;   
    try {   
        bi = ImageIO.read(f);   
        ByteArrayOutputStream baos = new ByteArrayOutputStream();   
        ImageIO.write(bi, "jpg", baos);   
        byte[] bytes = baos.toByteArray();   
           
        return encoder.encodeBuffer(bytes).trim();   
    } catch (IOException e) {   
        e.printStackTrace();   
    }   
    return null;   
}   
   
static void base64StringToImage(String base64String){   
    try {   
        byte[] bytes1 = decoder.decodeBuffer(base64String);   
           
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes1);   
        BufferedImage bi1 =ImageIO.read(bais);   
        File w2 = new File("f://meinv.bmp");//可以是jpg,png格式   
        ImageIO.write(bi1, "jpg", w2);//不管输出什么格式图片,此处不需改动   
    } catch (IOException e) {   
        e.printStackTrace();   
    }   
}   
}

4.Java的的反射实现是委派实现还是动态实现?委派实现和动态实现是按照什么机制去识别实现的?即什么情况下用委派实现,什么情况下动态实现?

如果对什么是线程、什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内。

用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。

很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。

多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
并行与并发:
    并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
    并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
并发与并行

线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:

void transferMoney(User from, User to, float amount){
  to.setMoney(to.getBalance() + amount);
  from.setMoney(from.getBalance() - amount);
}

同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
好了,让我们开始吧。我准备分成几部分来总结涉及到多线程的内容:

扎好马步:线程的状态
内功心法:每个对象都有的方法(机制)
太祖长拳:基本线程类
九阴真经:高级多线程控制类
扎好马步:线程的状态

先来两张图:

线程状态

线程状态转换

各种状态一目了然,值得一提的是"blocked"这个状态:
线程在Running的过程中可能会遇到阻塞(Blocked)情况

调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。
此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。
内功心法:每个对象都有的方法(机制)

synchronized, wait, notify 是任何对象都具有的同步工具。让我们先来了解他们

monitor

他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。

wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。

当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

再讲用法:

synchronized单独使用:
    代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容


    public class Thread1 implements Runnable {
       Object lock;
       public void run() {  
           synchronized(lock){
             ..do something
           }
       }
    }

    直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。

    public class Thread1 implements Runnable {
       public synchronized void run() {  
            ..do something
       }
    }

synchronized, wait, notify结合:典型场景生产者消费者问题


/**
   * 生产者生产出来的产品交给店员
   */
  public synchronized void produce()
  {
      if(this.product >= MAX_PRODUCT)
      {
          try
          {
              wait();  
              System.out.println("产品已满,请稍候再生产");
          }
          catch(InterruptedException e)
          {
              e.printStackTrace();
          }
          return;
      }

      this.product++;
      System.out.println("生产者生产第" + this.product + "个产品.");
      notifyAll();   //通知等待区的消费者可以取出产品了
  }

  /**
   * 消费者从店员取产品
   */
  public synchronized void consume()
  {
      if(this.product <= MIN_PRODUCT)
      {
          try 
          {
              wait(); 
              System.out.println("缺货,稍候再取");
          } 
          catch (InterruptedException e) 
          {
              e.printStackTrace();
          }
          return;
      }

      System.out.println("消费者取走了第" + this.product + "个产品.");
      this.product--;
      notifyAll();   //通知等待去的生产者可以生产产品了
  }


volatile

多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。
volatile

针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。
太祖长拳:基本线程类

基本线程类指的是Thread类,Runnable接口,Callable接口
Thread 类实现了Runnable接口,启动一个线程的方法:

 MyThread my = new MyThread();
  my.start();

Thread类相关方法:


//当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)
public static Thread.yield() 
//暂停一段时间
public static Thread.sleep() 
//在一个线程中调用other.join(),将等待other执行完后才继续本线程。    
public join()
//后两个函数皆可以被打断
public interrupte()



关于中断:它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。
Thread.interrupted()检查当前线程是否发生中断,返回boolean
synchronized在获锁的过程中是不能被中断的。

中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体

Thread类最佳实践:
写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。

如何获取线程中的异常

不能用try,catch来获取线程中的异常
Runnable

与Thread类似
Callable

future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态

ExecutorService e = Executors.newFixedThreadPool(3);
//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 无阻塞
future.get() // return 返回值,阻塞直到该线程运行结束

九阴真经:高级多线程控制类

以上都属于内功心法,接下来是实际项目中常用到的工具了,Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。
1.ThreadLocal类

用处:保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。

实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。
主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。
2.原子类(AtomicInteger、AtomicBoolean……)

如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized

//返回值为boolean
AtomicInteger.compareAndSet(int expect,int update)

该方法可用于实现乐观锁,考虑文中最初提到的如下场景:a给b付款10元,a扣了10元,b要加10元。此时c给b2元,但是b的加十元代码约为:


if(b.value.compareAndSet(old, value)){
return ;
}else{
//try again
// if that fails, rollback and log
}



AtomicReference
对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。
这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号
3.Lock类 

lock: 在java.util.concurrent包内。共有三个实现:

ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。

区别如下:


lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)
提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
本质上和监视器锁(即synchronized是一样的)
能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。
和Condition类的结合。
性能更高,对比如下图:



synchronized和Lock性能对比

ReentrantLock    
可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。
使用方法是:

1.先new一个实例

static ReentrantLock r=new ReentrantLock();

2.加锁      

r.lock()或r.lockInterruptibly();

此处也是个不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception 或catch)    

3.释放锁   

r.unlock()

必须做!何为必须做呢,要放在finally里面。以防止异常跳出了正常流程,导致灾难。这里补充一个小知识点,finally是可以信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。

ReentrantReadWriteLock

可重入读写锁(读写锁的一个实现) 

 ReentrantReadWriteLock lock = new ReentrantReadWriteLock()
  ReadLock r = lock.readLock();
  WriteLock w = lock.writeLock();

两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码
4.容器类

这里就讨论比较常用的两个:

BlockingQueue
ConcurrentHashMap

BlockingQueue
阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管  道,特别适用于先进先出策略的一些应用场景。普通的queue接口主要实现有PriorityQueue(优先队列),有兴趣可以研究

BlockingQueue在队列的基础上添加了多线程协作的功能:

BlockingQueue

除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。

常见的阻塞队列有:

ArrayListBlockingQueue
LinkedListBlockingQueue
DelayQueue
SynchronousQueue

ConcurrentHashMap
高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap
5.管理类

管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。
了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBean
ThreadPoolExecutor
如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:


ExecutorService e = Executors.newCachedThreadPool();

ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool(3);
// 第一种是可变大小线程池,按照任务数来分配线程,
// 第二种是单线程池,相当于FixedThreadPool(1)
// 第三种是固定大小线程池。
// 然后运行
e.execute(new MyRunnableImpl());


该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。请参见javadoc:

ThreadPoolExecutor参数解释

翻译一下:


corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。
maximumPoolSize:线程最大值,线程的增长始终不会超过该值。
keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态
unit:
时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS 
workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)
threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。

6.想做延迟任务,比如今天下发任务到每个单位,截止明天上午10点必须汇报,否则就短信提示。想使用rabbitmq实现,目前了解到rabbitmq队列,昨天在先队列中放了10点结束的,今天放入队列一个9点的,但是由于队列的特性,只能等10点先出队列,九点的才能出来,又不想用对个队列接受不同时间的的任务,请问有别的方案吗?

可以用 Dead Letter Exchanges 来实现。
使用RabbitMQ实现延迟任务场景一:物联网系统经常会遇到向终端下发命令,如果命令一段时间没有应答,就需要设置成超时。
场景二:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单。上述类似的需求是我们经常会遇见的问题。最常用的方法是定期轮训数据库,设置状态。在数据量小的时候并没有什么大的问题,但是数据量一大轮训数据库的方式就会变得特别耗资源。当面对千万级、上亿级数据量时,本身写入的IO就比较高,导致长时间查询或者根本就查不出来,更别说分库分表以后了。
除此之外,还有优先级队列,基于优先级队列的JDK延迟队列,时间轮等方式。但如果系统的架构中本身就有RabbitMQ的话,那么选择RabbitMQ来实现类似的功能也是一种选择。使用RabbitMQ来实现延迟任务必须先了解RabbitMQ的两个概念:消息的TTL和死信Exchange,通过这两者的组合来实现上述需求。消息的TTL(Time To Live)消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。
对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。只是expiration字段是字符串参数,所以要写个int类型的字符串:byte[] messageBodyBytes = "Hello, world!".getBytes();AMQP.BasicProperties properties = new AMQP.BasicProperties();properties.setExpiration("60000");channel.basicPublish("my-exchange", "routing-key", properties, messageBodyBytes);当上面的消息扔到队列中后,过了60秒,如果没有被消费,它就死了。不会被消费者消费到。这个消息后面的,没有“死掉”的消息对顶上来,被消费者消费。死信在队列中并不会被删除和释放,它会被统计到队列的消息数中去。单靠死信还不能实现延迟任务,还要靠Dead Letter Exchange。Dead Letter ExchangesExchage的概念在这里就不在赘述,可以从这里进行了解。一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。1. 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。2. 上面的消息的TTL到了,消息过期了。3. 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。实现延迟队列延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列。 生产者输出消息到Queue1,并且这个消息是设置有有效时间的,比如60s。消息会在Queue1中等待60s,如果没有消费者收掉的话,它就是被转发到Queue2,Queue2有消费者,收到,处理延迟任务。具体实现步骤如下:第一步, 首先需要创建2个队列。Queue1和Queue2。Queue1是一个消息缓冲队列,在这个队列里面实现消息的过期转发。如下图,设置Dead letter exchange和Dead letter routing key。设置这两个属性就是当消息在这个队列中expire后,采用哪个路由发送。这个dlx的exchange需要事先创建好,就是一个普通的exchange。由于我们还需要向Queue1发送消息,那么还需要创建一个exchange,并且和Queue1绑定。例子中,exchange同样取名:queue1。我们还需要建一个Queue2,这个队列用于消息在Queue1中过期后转发的目标队列。所以这个Queue2队列建好以后,需要绑定Queue1设置的死信路由:dlx。完成Queue2的绑定以后,环境就搭建完成了。 第二步,实现消息的Producer。由于我们的目的是让进入Queue1的消息过期,然后自动转送到Queue2中,所以发送的时候,需要设置过期时间。ConnectionFactory factory = new ConnectionFactory();            factory.setUsername("bsp");            factory.setPassword("123456");            factory.setVirtualHost("/");            factory.setHost("10.23.22.42");            factory.setPort(5672);            conn = factory.newConnection();            channel = conn.createChannel();            byte[] messageBodyBytes = "Hello, world!".getBytes();            byte i = 10;            while (i-- > 0) {                                channel.basicPublish("queue1", "queue1", new AMQP.BasicProperties.Builder().expiration(String.valueOf(i * 1000)).build(),                        new byte[] { i });            }上面的代码我模拟了1-10号消息,消息的内容里面是1-10。过期的时间是10-1秒。这里要注意,虽然10是第一个发送,但是它过期的时间最长。 第三步,实现消息的Consumer。Consumer就是延迟任务的具体实施者。由于具体的任务往往是一个比较耗时的任务,所以一般来说,任务一般在异步线程中执行。ConnectionFactory factory = new ConnectionFactory();factory.setUsername("bsp");factory.setPassword("123456");factory.setVirtualHost("/");factory.setHost("10.23.22.42");factory.setPort(5672);conn = factory.newConnection();channel = conn.createChannel();channel.basicConsume("queue2", true, "consumer", new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {long deliveryTag = envelope.getDeliveryTag();                    //do some work asyncSystem.out.println(body[0]);}}); 运行后如上面的程序,过了10s以后,消费者开始收到数据,但是它是一次性收到如下结果:10、9 、8 、7 、6、5 、4 、3 、2 、1Consumer第一个收到的还是10。虽然10是第一个放进队列,但是它的过期时间最长。所以由此可见,即使一个消息比在同一队列中的其他消息提前过期,提前过期的也不会优先进入死信队列,它们还是按照入库的顺序让消费者消费。如果第一进去的消息过期时间是1小时,那么死信队列的消费者也许等1小时才能收到第一个消息。参考官方文档发现“Only when expired messages reach the head of a queue will they actually be discarded (or dead-lettered).”只有当过期的消息到了队列的顶端(队首),才会被真正的丢弃或者进入死信队列。所以在考虑使用RabbitMQ来实现延迟任务队列的时候,需要确保业务上每个任务的延迟时间是一致的。如果遇到不同的任务类型需要不同的延时的话,需要为每一种不同延迟时间的消息建立单独的消息队列。

7.服务器频繁宕机不响应,一台nginx多台tomcat ,隔一两天就有一台甚至更多tomcat 不响应,宕机时看代码运行日志,显示最后几行日志是准备往数据库更新某行记录的状态,更新一直没结束,单表操作。直接用curl调用其他页面请求也是一直不响应。查看服务器网络状态CLOSE-WAIT两千多。数据库操作用的hibernate

CLOSE_WAIT 过多主要是由于服务器被动关闭连接时,程序处理不当产生的。可以往以下几个方面进行考虑:
1.是否有资源连接没有释放
2.是否需要修改超时时间
3.是否需要修改连接数

8.工程部署运行一段时间后,占用内存越来越高,达到70~88%,jmap查看后,看到B、C、constMethodKlass等占用的内存情况,请问怎样再定位到代码层面?

[C 是 char[]
[B 是 byte[]
constMethodKlass 是被 Classloader 装载的代码优化的重点是字节数组和字符数组,
分析一下代码有哪些地方用户字节数组没有回收的,使用 String 没有回收的。

9.最近踩到了Log4j的坑,开启日志后,jmeter 1000并发没事,APp端FinalHTTP、OKHttp 一人访问就很容易出现死锁。

1.先说说排查死锁的问题可以使用通用的组合排查方式:
 jps + jstack 以下均在windows 的 cmd 下操作:
 1)jps -l :输出程序的包路径,比如,com.demo.Test
 2)jstack -l pid:打印 pid 对应的 Java堆栈信息,如果是死锁,你将看到如下信息:Found one Java-level deadlock
 2.再说说解决死锁的问题可以归结为以下三点:
 1)重新启动系统
 2)撤消进程3)进程回退但是不论你怎么解决死锁的问题,对于系统来说都是要付出代价的。最好的办法就是预防死锁的发生,理清楚业务的流程、注意同步时加锁的顺序等

10.现在都说前后端分离,我是想往Java后端方向去发展,正在学ssm框架整合开发,但是开发实战经验倒是少。现在又有springboot和springcloud等技术,还有redis等NoSQL技术,现在我的疑惑是没有明确的后端学习路线?

首先要明确这些技术解决什么问题,然后才能有所侧重Spring boot 其实就是 Spring,这是 Spring Boot 简化了 Spring 项目的整合配置成本。如果你今天开始搞 spring,没有历史遗留,那就从 Spring boot 开始好了,至于怎么学习,如果英文过关,直接阅读 Spring 官网的 reference 就好,写得最详细。如果希望能有一个一步一步的引导,Spring in Action 或者 Spring Boot in Action 都是不错的书籍,但是这些书都是为了入门不够全,更多的整合需要阅读 Reference。Spring Cloud 解决分布式系统的问题,如果你用 spring 不考虑弹性和高可用之类的问题,那就先不需要深入了。这个目前社区也有中文书了,可以搜索一下。redis 和 Spring 结合的场景常见的有两个,一个是作为 Spring Session 的 cache ,一个是作为普通的 KV,前者有 Spring-session 项目,后者有 Spring-data-redis 项目,可以了解一下。

11.您好,最近在阅读spring源码的时候遇到一些问题,这个框架太大了,请问下如何系统性的阅读它的源码,该怎么入手怎么阅读呢。

首先说老实话我没有系统性的阅读过,一般是碰到了问题追进去看。单单看 spring framework 最核心的依赖注入和 bean 管理应该还好,要是阅读 spring security 那就比较晦涩了。虽然我觉得阅读 spring framework 的源码的初衷是什么?如果是为了能提升自己面向对象的素养,或者提升自己对大框架的理解能力,那我觉得还是值得一读的。如果只是为了更好的熟悉 Spring framework,那我觉得不如多读几遍 reference。Spring 的 reference 写的非常好,还是就是 spring 团队的 blog 也非常的不错。如果你觉得真的想锻炼一下自己,那我给出如下建议
一. 先把 reference 看熟悉吧,知道怎么使用,对于背后的机制有帮助
二. 设计模式先看一遍,spring 源码中的设计模式还是蛮多的,熟悉的话,看看类名就知道在干嘛了。
三. 写个 Helloworld 的例子,然后把牵扯到的 spring 背后的源码读一遍,搞清楚原理。
四. 找一个画类图的工具,复杂的类继承和依赖关系画出来,然后从核心模块开始逐步的读。我当年读 eclipse 源码的时候是这么干的,过程很辛苦,但是前提是文档资料太少,我觉得文档够多的话,其实没有必要通读,搞清楚背后原理更实用些。

12.为何字符串比较的时候要用equals,而不用==

对于引用数据类型(类、接口类型、数组类型、枚举类型、注解类型)来说,==和equals都是来比较两个变量的地址,String类重写了equals方法,所以可以比较两个字符串内容是否相等 这里判断的是字符串是否匹配,所以需要用equals方法而非“==”

13.如何解决运行java时报错:unable to load native library: libjava.jnilib

 .bash_profile 中设置了 DYLD_LIBRARY_PATH,可以试着取消 DYLD_LIBRARY_PATH 的设置。

14.为什么流式处理框架都是 java 写成的,JVM 是不是在流和批存在着特殊优势。还有分布式资源调度,感觉Mesos 的成长速度跟不上 Yarn。这是为什么?

1.这个和storm、spark这两个鼻祖有不可分割的关系。java8开始提供了很多流和的features。
 2.关于 mesos 和 yarn的比较 加你你看看这篇文章 https://www.oreilly.com/ideas/a-tale-of-two-clusters-mesos-and-yarn ,Yarn的信息量级大一级。

15.java通过jni调用c++本地库函数,c++程序崩溃,如何保障jvm不会崩?

简单的说 JNI 的调用崩溃了,就会导致 JVM Crash,因为失去了Java 那一层的保护了。我不是这方面的专家,我在社区找到了一个类似的问题,希望对你有帮助,https://stackoverflow.com/questions/44062332/jvm-crashes-when-calling-jni-function-during-gc

16.MYSQL中时间戳如何转化为标准时间?

方法有2种:
1)使用函数FROM_UNIXTIME()。SELECT FROM_UNIXTIME(时间戳的值)执行即可。
2)自己写SQL实现转换,UNIX_TIMESTAMP,Unix时间戳格式从1970-01-01的0时刻开始,select FROM_UNIXTIME(时间戳/1000, '格式化字符串') as 标准时间from 表3)也可以使用Java或者其它语言代码转换。mysql中存入的时间戳是10位的int,时间戳表示的是从1970-01-01开始经过的毫秒数。我们现在反向计算出标准时间即可。Java标准时间戳=小数点左边数据*1000+小数点右边的值 ;String timestamp=mysql时间戳+"000";  //放大1000long lTime = Long.parseLong(timestamp);  //java标准时间戳SimpleDateFormat standardTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//转换为Java标准日期

17.java小白如何快速学会spring.io呢?

买本 《spring in action》 的书来看,快速了解个大概。spring.io 上有很多 spring 的衍生项目,这个要啃其官方的 reference,然后去 github 上找 spring 官方给出的 sample 项目看看。

18.java怎么高效的判断字符串是否数字(包括正负数、整数、小数)

//可以用正则表达式的方式来判断,可以判断正负、整数小数Boolean isNumber = str.matches("-?[0-9]+.[0-9]");

19.对于JAVA后期应该如何进阶?

Java 语言本身
1.  查漏补缺,把 SDK 里常用的方法的源码都看看。然后搞清楚每个版本都引入了那些新特性,这些新特性解决什么问题,如何使用。
2.  分布式计算,去看看 Spring cloud 相关的东西
3.  Java 调优,深入到虚拟机的细节,不同的垃圾回收器的工作原理,一堆虚拟机参数该怎么配置。通用能力
4.  数据结构和算法,java 里常见的算法怎么实现的,比如 java 的排序实现,Map 的数据结构
5.  设计模式

20.JAVA中,应该怎样实现深拷贝和浅拷贝?

深拷贝(深度克隆):不仅复制对象的值类型字段,同时也复制原对象中的对象。就是说完全是新对象产生的。一种可行的方式是让类实现 Cloneable 接口,并覆盖 clone 方法。但如果类中有多个引用类型属性,这些对象有可能也会有其他的引用类型属性,那么上面这种做法就要去所有的相关类都要实现 Cloneable 接口,并覆盖 clone 方法,不仅麻烦,而且非常不利于后期维护和扩展。在Java语言里深拷贝一个对象,利用序列化来做,这是常用的方法。可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
浅拷贝:只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化;深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变。浅拷贝的方式,直接引入赋值即可。深拷贝可以利用java对象的Clone方法重写实现。
1. 深拷贝和浅拷贝 简单理解为深度拷贝和一层拷贝。区别在于浅拷贝不会拷贝嵌套属性对象只会复制引用。
2. 实现:原理如上,按照原理实现即可。
3. Java中的clone为浅拷贝。

21.Java Applet和Java Application的共同之处

Java Application是何种类型,GUI、WEB。 Applet、Application最大的不同是,一个是嵌入道浏览器执行的,一个是可以脱离浏览器执行(但两者都需要JRE的环境支持) 还有,Applet的执行方法是init(),而application是main函数入口(本质上,应该都是main函数入口)

《参考:https://yq.aliyun.com/search/asks/?q=》

你可能感兴趣的:(JAVA开发)