Borrowing almost verbatim from a recent response about finding [extrema](https://mathematica.stackexchange.com/questions/5575/5591#5591), here is a method that is useful when your function is differentiable and hence can be "tracked" by `NDSolve`.

 f[x_] := BesselJ[1, x]^2 + BesselK[1, x]^2 - Sin[Sin[x]]
 
 In[191]:= zeros = 
 Reap[soln = 
 y[x] /. First[
 NDSolve[{y'[x] == Evaluate[D[f[x], x]], y[10] == (f[10])}, 
 y[x], {x, 10, 0}, 
 Method -> {"EventLocator", "Event" -> y[x], 
 "EventAction" :> Sow[{x, y[x]}]}]]][[2, 1]]
 
 
 During evaluation of In[191]:=
 NDSolve::mxst: Maximum number of 10000 steps reached at the point
 x == 1.5232626281716416`*^-124. >>
 
 Out[191]= {{9.39114, 8.98587*10^-16}, {6.32397, -3.53884*10^-16},
 {3.03297, -8.45169*10^-13}, {0.886605, -4.02456*10^-15}}
 
 Plot[f[x], {x, 0, 10}, 
 Epilog -> {PointSize[Medium], Red, Point[zeros]}]

![function and roots](https://i.sstatic.net/vFe5w.png)

If it were a trickier function, one might use `Method -> {"Projection", ...}` to enforce the condition that `y[x]` is really the same as `f[x]`. This method may be useful in situations (if you can find them) where we have one function in one variable, and `Reduce` either cannot handle it or takes a long time to do so.

---

**Addendum by J. M.**

`WhenEvent` is now the documented way to include event detection in `NDSolve`, so using it along with the trick of [specifying an empty list where the function should be](https://reference.wolfram.com/language/tutorial/NDSolveWhenEvents.html#32663), here's how to get a pile of zeroes:

 f[x_] := BesselJ[1, x]^2 + BesselK[1, x]^2 - Sin[Sin[x]]
 
 zeros = Reap[NDSolve[{y'[x] == D[f[x], x], WhenEvent[y[x] == 0, Sow[{x, y[x]}]],
 y[10] == f[10]}, {}, {x, 10, 0}]][[-1, 1]];
 
 Plot[f[x], {x, 0, 10}, Epilog -> {PointSize[Medium], Red, Point[zeros]}]
![function and roots](https://i.sstatic.net/tVrQJ.png)